函數指針是一個好東西。
一、簡述
函數指針就是執行一段函數代碼的指針。
根據官方的説法,函數指針實現了FnOnce,FnMut,Fn特質。
由於函數指針是一種數據類型,所以rustc允許它作為函數/方法的參數,這樣就給程序設計添加了不少的靈活性.
我估摸着,rust設計者就是為了讓rust能夠適應潮流:在函數/方法中直接傳遞匿名函數/閉包
一個典型的帶有函數指針的rust函數定義如下:
void Fight(Tool tool,Target tar)
它看起來不如rust來的那麼明顯。rust的定義能夠讓我立刻意識到這是函數指針參數(意味着這是一段代碼)。
當然,只要願意,應該也可以把函數指針以不那麼明顯的方式定義,後文有例子.
二、示例
fn add_one(x: i32) -> i32 {
x + 1
}
fn multiply_10(x: i32) -> i32 {
x * 10
}
type FnPointer = fn(i32,i32) -> String;
fn link(f: FnPointer, x:i32,y:i32) {
let dst= f(x,y);
println!("{}",dst);
}
/**
* 第一個參數是函數指針,fn指針實現了FnOnce,FnMut,Fn特質,所以可以接受匿名函數作為參數。
*/
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn 書本例子() {
let list_of_numbers = vec![1, 2, 3];
//利用rustc,象我這樣的傻瓜也能寫rust!
let list_of_strings: Vec<String> =
list_of_numbers.iter().map(|i| "0".to_owned()+&i.to_string()).collect();
println!("{:?}", list_of_strings);
let list_of_strings2: Vec<String> =
list_of_numbers.iter().map(ToString::to_string).collect();
println!("{:?}", list_of_strings2);
}
fn main() {
//封裝再封裝,就可以解決一些複雜問題,至於是否高效是另外一回事了.
let funcs=[add_one, multiply_10];
let numbers = [123, 233, 3232, 32334, 5333];
for i in 0..numbers.len() {
let answer1 = do_twice(funcs[0], numbers[i]);
let answer2= do_twice(funcs[1], numbers[i]);
println!("({}+1)*2= {},({}*10)*2={}",numbers[i],answer1,numbers[i],answer2);
}
// do_twice同時也能接受匿名函數,rust的解釋:fn指針實現了FnOnce,FnMut,Fn特質,所以可以接受匿名函數作為參數。
println!("\n匿名函數也能被接受作為參數的演示....");
let answer3 = do_twice(|x| x * 2+99, numbers[0]);
println!("{}經過處理後等於{}",numbers[0],answer3);
// 書本例子,讓我們感興趣是類似java等語言的流式操作
書本例子();
//類型別名
let link_to = |x:i32,y:i32| x.to_string()+&y.to_string();
link(link_to, 123,899);
}
上例基本模仿書本,主要演示兩個問題:
1.函數指針如何定義
2.使用函數指針作為參數的函數,也能接收匿名函數為入參
另外順便演示瞭如何讓函數指針看起來不那麼明顯(效果上類似java等一些語言),以下代碼用於達成這個目的:
type FnPointer = fn(i32,i32) -> String;
fn link(f: FnPointer, x:i32,y:i32) {
let dst= f(x,y);
println!("{}",dst);
}
如果函數指針比較複雜,也許用上別名也不錯。
輸出結果:

三、函數指針的替代方案
在函數/方法中除了可以通過指定函數指針的方式來傳遞函數,其實還有幾種方式:
1.利用特質綁定+通用類型
2.還是特質綁定+通用類型,只不過是另外一種書寫形式
3.使用特質對象+動態分發dyn
以下就是例子:
/**
* 函數指針的替代者1 -- 利用特質綁定
*/
fn batman<T: FnOnce(i32,i32)->String>(f:T) {
let result=f(1094,101);
println!("{}",result);
}
/**
* 函數指針的替代者2 -- 特質綁定的簡化寫法,rust 1.13引入的trait bound syntax
*/
fn eggman(f:impl FnOnce(i32,i32)->String) {
let result=f(1094,103);
println!("{}",result);
}
/**
* 函數指針的替代者3 -- 使用特質對象
*/
fn pigman(f:&dyn Fn(i32,i32)->String) {
let result=f(2094,103);
println!("{}",result);
}
fn main(){
batman(|x:i32,y:i32| x.to_string()+&y.to_string()+" 黑蝙蝠俠 🦇");
eggman(|x:i32,y:i32| x.to_string()+&y.to_string()+" 蛋蛋大俠 🥚🥚");
pigman(&|x:i32,y:i32| x.to_string()+&y.to_string()+" 豬豬俠士 🐷");
}
輸出結果:

這裏例子中,比較特殊的就是pigman的定義:
這裏的定義比較特殊,用上了&dyn,為什麼要這樣了?
- 因為特質對象必須使用動態分發,這是rust規定的
- 要使用&,是因為特質對象大小不確定,rustc不知道大小,所以只能加一個引用,以便構成一個指針(編譯器可以知道大小)
最後,pigman用了Fn,而不是FnOnce,這是因為FnOnce要求移出,要求移出則必須實現Copy。為了不Copy,所以改為Fn。
記住,rust中函數/方法本身也是一種類型。
四、小結
1.函數指針fn的存在某種程度上方便了程序設計-這讓程序設計更加靈活
2.函數指針實現了FnOnce,FnMut,Fn,按照rust的説法就是可以定義一個匿名函數作為函數指針傳遞給函數/方法
3.函數指針的存在,使得編寫代碼某種程度上方便了部分程序員
4.事實上,就是沒有函數指針,其實也有其它方式能過實現類似的效果