1. 程式人生 > 其它 >2020-10-29

2020-10-29

函式指標

我們討論過了如何向函式傳遞閉包;也可以向函式傳遞常規函式!這在我們希望傳遞已經定義的函式而不是重新定義閉包作為引數時很有用。通過函式指標允許我們使用函式作為另一個函式的引數。函式的型別是fn(使用小寫的 “f” )以免與Fn閉包 trait 相混淆。fn被稱為函式指標function pointer)。指定引數為函式指標的語法類似於閉包,如示例 19-27 所示:

檔名: src/main.rs

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {}", answer);
}

示例 19-27: 使用fn型別接受函式指標作為引數

這會打印出The answer is: 12do_twice中的f被指定為一個接受一個i32引數並返回i32fn。接著就可以在do_twice函式體中呼叫f。在main中,可以將函式名add_one作為第一個引數傳遞給do_twice

不同於閉包,fn是一個型別而不是一個 trait,所以直接指定fn作為引數而不是宣告一個帶有Fn作為 trait bound 的泛型引數。

函式指標實現了所有三個閉包 trait(FnFnMutFnOnce),所以總是可以在呼叫期望閉包的函式時傳遞函式指標作為引數。傾向於編寫使用泛型和閉包 trait 的函式,這樣它就能接受函式或閉包作為引數。

一個只期望接受fn而不接受閉包的情況的例子是與不存在閉包的外部程式碼互動時:C 語言的函式可以接受函式作為引數,但 C 語言沒有閉包。

作為一個既可以使用內聯定義的閉包又可以使用命名函式的例子,讓我們看看一個map的應用。使用map函式將一個數字 vector 轉換為一個字串 vector,就可以使用閉包,比如這樣:


let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
    .iter()
    .map(|i| i.to_string())
    .collect();

或者可以將函式作為map的引數來代替閉包,像是這樣:


let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
    .iter()
    .map(ToString::to_string)
    .collect();

注意這裡必須使用“高階 trait”部分講到的完全限定語法,因為存在多個叫做to_string的函式;這裡使用了定義於ToStringtrait 的to_string函式,標準庫為所有實現了Display的型別實現了這個 trait。

另一個實用的模式暴露了元組結構體和元組結構體列舉成員的實現細節。這些項使用()作為初始化語法,這看起來就像函式呼叫,同時它們確實被實現為返回由引數構造的例項的函式。它們也被稱為實現了閉包 trait 的函式指標,並可以採用類似如下的方式呼叫:


enum Status {
    Value(u32),
    Stop,
}

let list_of_statuses: Vec<Status> =
    (0u32..20)
    .map(Status::Value)
    .collect();

這裡建立了Status::Value例項,它通過map用範圍的每一個u32值呼叫Status::Value的初始化函式。一些人傾向於函式風格,一些人喜歡閉包。這兩種形式最終都會產生同樣的程式碼,所以請使用對你來說更明白的形式吧。