高階函數

高階函數與普通函數的不同在於,它可以使用一個或多個函數作為參數,可以將函數作為返回值。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就是一個高階函數,它有兩個參數,一個類型為i32n,另一個類型為fn(i32)->i32的函數func,返回一個i32類型的參數;它在函數體內以n作為參數調用func函數,返回func函數的返回值。運行可以得到以下結果:

3 + 1 = 4
3 - 1 = 2

不過,這不是函數作為參數的唯一聲明方法,使用泛型函數配合特質(trait)也是可以的,因為rust的函數都會實現一個trait:FnOnceFnFnMut。將上例中的process函數定義換成以下形式是等價的:

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編譯,會出現如下錯誤: error

results matching ""

    No results matching ""