高階函數
高階函數與普通函數的不同在於,它可以使用一個或多個函數作為參數,可以將函數作為返回值。rust的函數是first class type,所以支持高階函數。而,由於rust是一個強類型的語言,如果要將函數作為參數或返回值,首先需要搞明白函數的類型。下面先說函數的類型,再說函數作為參數和返回值。
函數類型
前面說過,關鍵字fn可以用來定義函數。除此以外,它還用來構造函數類型。與函數定義主要的不同是,構造函數類型不需要函數名、參數名和函數體。在Rust Reference中的描述如下:
The function type constructor fn forms new function types. A function type consists of a possibly-empty set of function-type modifiers (such as unsafe or extern), a sequence of input types and an output type.
來看一個簡單例子:
fn inc(n: i32) -> i32 {//函數定義 n + 1 } type IncType = fn(i32) -> i32;//函數類型 fn main() { let func: IncType = inc; println!("3 + 1 = {}", func(3)); }
上例首先使用fn定義了inc函數,它有一個i32類型參數,返回i32類型的值。然後再用fn定義了一個函數類型,這個函數類型有i32類型的參數和i32類型的返回值,並用type關鍵字定義了它的別名IncType。在main函數中定義了一個變量func,其類型就為IncType,並賦值為inc,然後在pirntln宏中調用:func(3)。可以看到,inc函數的類型其實就是IncType。
這裡有一個問題,我們將inc賦值給了func,而不是&inc,這樣是將inc函數的擁有權轉給了func嗎,賦值後還可以以inc()形式調用inc函數嗎?先來看一個例子:
fn main() { let func: IncType = inc; println!("3 + 1 = {}", func(3)); println!("3 + 1 = {}", inc(3)); } type IncType = fn(i32) -> i32; fn inc(n: i32) -> i32 { n + 1 }
我們將上例保存在rs源文件中,再用rustc編譯,發現並沒有報錯,並且運行也得到我們想要的結果:
3 + 1 = 4
3 + 1 = 4
這說明,賦值時,inc函數的所有權並沒有被轉移到func變量上,而是更像不可變引用。在rust中,函數的所有權是不能轉移的,我們給函數類型的變量賦值時,賦給的一般是函數的指針,所以rust中的函數類型,就像是C/C++中的函數指針,當然,rust的函數類型更安全。可見,rust的函數類型,其實應該是屬於指針類型(Pointer Type)。rust的Pointer Type有兩種,一種為引用(Reference&),另一種為原始指針(Raw pointer *),詳細內容請看Rust Reference 8.18 Pointer Types。而rust的函數類型應是引用類型,因為它是安全的,而原始指針則是不安全的,要使用原始指針,必須使用unsafe關鍵字聲明。
函數作為參數
函數作為參數,其聲明與普通參數一樣。看下例:
fn main() { println!("3 + 1 = {}", process(3, inc)); println!("3 - 1 = {}", process(3, dec)); } fn inc(n: i32) -> i32 { n + 1 } fn dec(n: i32) -> i32 { n - 1 } fn process(n: i32, func: fn(i32) -> i32) -> i32 { func(n) }
例子中,process就是一個高階函數,它有兩個參數,一個類型為i32的n,另一個類型為fn(i32)->i32的函數func,返回一個i32類型的參數;它在函數體內以n作為參數調用func函數,返回func函數的返回值。運行可以得到以下結果:
3 + 1 = 4
3 - 1 = 2
不過,這不是函數作為參數的唯一聲明方法,使用泛型函數配合特質(trait)也是可以的,因為rust的函數都會實現一個trait:FnOnce、Fn或FnMut。將上例中的process函數定義換成以下形式是等價的:
#![allow(unused)] fn main() { fn process<F>(n: i32, func: F) -> i32 where F: Fn(i32) -> i32 { func(n) } }
函數作為返回值
函數作為返回值,其聲明與普通函數的返回值類型聲明一樣。看例子:
fn main() { let a = [1,2,3,4,5,6,7]; let mut b = Vec::<i32>::new(); for i in &a { b.push(get_func(*i)(*i)); } println!("{:?}", b); } fn get_func(n: i32) -> fn(i32) -> i32 { fn inc(n: i32) -> i32 { n + 1 } fn dec(n: i32) -> i32 { n - 1 } if n % 2 == 0 { inc } else { dec } }
例子中的高階函數為get_func,它接收一個i32類型的參數,返回一個類型為fn(i32) -> i32的函數,若傳入的參數為偶數,返回inc,否則返回dec。這裡需要注意的是,inc函數和dec函數都定義在get_func內。在函數內定義函數在很多其他語言中是不支持的,不過rust支持,這也是rust靈活和強大的一個體現。不過,在函數中定義的函數,不能包含函數中(環境中)的變量,若要包含,應該閉包(詳看13章 閉包)。
所以下例:
fn main() { let f = get_func(); println!("{}", f(3)); } fn get_func() -> fn(i32)->i32 { let a = 1; fn inc(n:i32) -> i32 { n + a } inc }
使用rustc編譯,會出現如下錯誤:
