如何在 Rust 中定義高階函數

高階函數是其參數或返回值本身就是函數的函數。換句話說,如果一種語言支持高階函數,那麼我們就說這些函數是一等公民,也就是說它們是值。

在本文中,我們將研究 Rust 如何支持高階函數,以及如何定義它們。

Rust 中的函數

我們可以通過 fn 關鍵字在 Rust 中定義函數。像往常一樣,定義一個函數,我們必須指定它的名稱,參數和返回值的類型:

fn plus_one(n: i32) -> i32 {
    n + 1
}

return 關鍵字是可選的。如果我們不指定它,函數的最後一條語句被認爲是返回語句。

如前所述,Rust 中的函數是一等公民。因此,我們可以將它們存儲在一個變量中。一旦它被存儲在變量中,我們可以像往常一樣調用它:

fn main() {
    let add_one = plus_one;

    println!("{}", add_one(1));
}

函數作爲參數

我們演示瞭如何定義函數並將其存儲在變量中。現在,讓我們看看如何將一個函數作爲參數傳遞給另一個函數。

首先,我們定義一個高階函數:

fn binary_operator(n: i32, m: i32, op: F) -> i32 
where F: Fn(i32, i32) -> i32 {
    op(n, m)
}

binary_operator 輸入兩個數字 n 和 m,以及一個函數 op。它對 n 和 m 應用 op,返回結果。

注意 op 參數的類型。它是一個泛型類型 F,在 binary_operator 的 where 子句中進行了細化。特別地,我們將其定義爲具有兩個數值形參 (i32, i32) 的函數 Fn,返回形參 i32。Fn 在這裏表示一個函數指針,即存儲函數的內存地址。

實名函數

將函數作爲參數傳遞的最簡單方法是使用它的名稱 (named function):

add(n: i32, m: i32) -> i32 {
    n + m
}

fn binary_operator(n: i32, m: i32, op: F) -> i32 
where F: Fn(i32, i32) -> i32 {
    op(n, m)
}

fn main() {
    println!("{}", binary_operator(5, 6, add));
}

在上面的例子中,我們首先定義一個函數 add 來相加兩個數字,並將其用作 binary_operator 的參數。結果打印 11,如預期。

匿名函數

有時候沒有必要給函數命名。例如,我們可能想要動態地定義一個函數,只在一個地方使用。這就是匿名函數發揮作用的地方:

fn main() {
    println!("{}", binary_operator(5, 6, |a: i32, b: i32| a - b));
}

在上面的例子中,我們在調用 binary_operator 時直接定義了一個匿名函數。|| 之間是參數列表,然後是函數體本身。

匿名函數是一個非常強大的工具,因爲在 Rust 中,它們可以 “捕獲” 上下文環境。在這種情況下,函數也稱爲閉包。

正如預期的那樣,上面的代碼段編譯並輸出 - 1。

函數作爲返回值

正如我們前面提到的,在 Rust 中一個函數也可以返回另一個函數。由於 Rust 中內存管理的結果,這有點複雜,我們很快就會看到。

在下面的例子中,我們將修改上面定義的 binary_operator,重新定義一個 unapplied_binary_operator 函數:

fn unapplied_binary_operator<'a, F>(n:&'a i32, m:&'a i32, op:&'a F) -> Box<dyn Fn() -> i32 + 'a>
where F: Fn(i32, i32) -> i32 {
    Box::new(move || op(*n, *m))
}

unapplied_binary_operator 的定義現在看起來要複雜得多。

返回函數的主要問題是定義函數生命週期的長度。在 Rust 中,生命週期是借用檢查器用來確保所有借用都是有效的。

在上面的例子中,我們定義了一個生命週期'a 和泛型的 F 類型。然後,我們將'a 與三個參數以及函數的返回值關聯起來。

基本上,我們告訴借用檢查器,只要三個參數 (n、m 和 op) 存在,就考慮 unapplied_binary_operator 返回的函數的生命期。

此外,Rust 中的生命週期只存在於引用中。因此,我們必須將參數和返回值分別用 & 和 Box 轉換爲引用。

一般來說,Box<dyn Fn()> 表示實現 Fn 特徵的裝箱值。

上述實現中另一個有趣的地方是 move 的使用。該關鍵字表示所有捕獲 (即對封閉環境的所有引用) 都是按值發生的。否則,只要返回的匿名函數存在,任何引用捕獲都將被刪除,從而使閉包保留無效引用。換句話說,通過 move,閉包獲得了它使用的變量的所有權。

unapplied_binary_operator 返回一個不帶參數的函數,返回 op 應用於 n 和 m 的結果。假設我們現在使用引用,我們必須使用借用來調用它:

fn main() {
    let n = 5;
    let m = 6;
    println!("{}", unapplied_binary_operator(&n, &m, &add)());
}

注意我們如何借用 n, m,並使用 &。最後,由於 unapplied_binary_operator 返回一個不帶參數的函數,我們可以使用空括號調用它。正如預期的那樣,上面的代碼片段將打印 11。

總結

在本文中,我們快速研究了 Rust 中的高階函數。我們演示瞭如何定義一個簡單的函數。然後,我們探索了將函數作爲參數傳遞的方法。最後,我們研究了返回一個函數有多複雜,簡要地提到了 Rust 的一些關鍵特性,比如借用和生命週期。

高階函數是 Rust 和許多其他編程語言中的一個關鍵特性;高階函數是函數式編程的一個基本概念。我們可以使用高階函數來編寫更簡潔、更易於長期維護的代碼。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/0TNzYHNlHNaD7eIPMguqIg