--- tags: RUST LANGUAGE --- # [Crust of Rust](/jLixlQ1ASZaBHqCBDFu1Iw) : Functions, Closures, and Their Traits ==[直播錄影](https://www.youtube.com/watch?v=QVK4Ooo_PqM)== * 主機資訊 ```rust wilson@wilson-HP-Pavilion-Plus-Laptop-14-eh0xxx ~/CrustOfRust> neofetch --stdout wilson@wilson-HP-Pavilion-Plus-Laptop-14-eh0xxx ----------------------------------------------- OS: Ubuntu 22.04.3 LTS x86_64 Host: HP Pavilion Plus Laptop 14-eh0xxx Kernel: 6.2.0-37-generic Uptime: 22 mins Packages: 2367 (dpkg), 11 (snap) Shell: bash 5.1.16 Resolution: 2880x1800 DE: GNOME 42.9 WM: Mutter WM Theme: Adwaita Theme: Yaru-dark [GTK2/3] Icons: Yaru [GTK2/3] Terminal: gnome-terminal CPU: 12th Gen Intel i5-12500H (16) @ 4.500GHz GPU: Intel Alder Lake-P Memory: 8569MiB / 15695MiB ``` * Rust 編譯器版本 : ```rust wilson@wilson-HP-Pavilion-Plus-Laptop-14-eh0xxx ~/CrustOfRust> rustc --version rustc 1.70.0 (90c541806 2023-05-31) (built from a source tarball) ``` ## Introduction [0:00:00](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=0s) In this episode, we go over the differences between function items, function pointers, and closures, as well as how they interact with the Fn* traits. We also touch upon dynamically dispatched Fns and experimental const Fn bounds. ## Rust for Rustaceans [0:01:20](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=80s) Jon 寫了 [Rust for Rustaceans](https://nostarch.com/rust-rustaceans),讀這本書之前必須先有先備知識,至少要看過 [The Rust Programming Language](https://doc.rust-lang.org/book/)。 ## Function items [0:02:48](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=148s) 開始建置 Rust 專案 : ``` shell $ cargo new --bin call-me $ cd eksempel $ vim src/main.rs ``` 看到主程式 : ```rust= fn main() { // main 是個函式,main 也有型別 println!("Hello, world!"); } ``` 觀察 `bar` 函式 : ```rust= fn main() { println!("Hello, world!"); let x = bar; // x 的型別為 fn bar(),看似函式指標,實則並不然。 // 它實際上是 function item,function item 與函式指標有點不同。 // 首先 function item 在編譯時期僅用於參考唯一函式 bar 的 0-sized 值。 } fn bar() {} ``` 如果 `bar` 函式為泛型,我們就不能將 `bar` 函式指派給變數 : ```diff= fn main() { println!("Hello, world!"); let x = bar; } -fn bar() {} +fn bar<T>() {} ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let x = bar; } fn bar<T>() {} ``` ::: 編譯出現以下錯誤 : ```rust $ cargo run ... error[E0282]: type annotations needed --> src\main.rs:3:13 | 3 | let x = bar; | ^^^ cannot infer type of the type parameter `T` declared on the function `bar` | help: consider specifying the generic argument | 3 | let x = bar::<T>; | +++++ ``` 編譯器有提示你需要傳入泛型參數。 嘗試重新將 x 指向使用不同泛型參數 instantiate 的 function item : ```diff= fn main() { println!("Hello, world!"); - let mut x = bar; + let mut x = bar::<i32>; x = bar::<u32>; } fn bar<T>() {} ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; x = bar::<u32>; } fn bar<T>() {} ``` ::: 編譯器會提示錯誤 : ```rust $ cargo run ... error[E0308]: mismatched types --> src\main.rs:4:9 | 3 | let mut x = bar::<i32>; | ---------- expected due to this value 4 | x = bar::<u32>; | ^^^^^^^^^^ expected `i32`, found `u32` | = note: expected fn item `fn() {bar::<i32>}` found fn item `fn() {bar::<u32>}` = note: different fn items have unique types, even if their signatures are the same = help: consider casting both fn items to fn pointers using `as fn()` ``` 會有這個錯誤是因為,`x` 並不是函式指標,而是 function item。 實際印出 x 占用的記憶體空間 : ```diff= fn main() { println!("Hello, world!"); let mut x = bar; + println!("{}", std::mem::size_of_val(&x)); - x = bar::<u32>; + // x = bar::<u32>; } fn bar<T>() {} ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; } fn bar<T>() {} ``` ::: 編譯並執行程式真的得到 0-sized : ```rust= $ cargo run ... Hello, world! 0 ``` `x` 根本不持有指標,因為 `bar::<i32>` 只是編譯器用來識別此函式的唯一 instance 的 identifier。 ## Function pointers [0:06:26](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=386s) Function item 具有為它們定義的強制轉換為函式指標 : ```diff= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; + baz(bar::<i32>); + baz(bar::<u32>); } -fn bar<T>() {} +fn bar<T>(_: u32) -> u32 { 0 } +fn baz(f: fn(u32) -> u32 ) {} ``` baz 接收的型別為函式指標,這時的 Line 6 與 Line 7 編譯器會將它們從 function item 強制轉換成函式指標。 驗證是否轉為函式指標,印出 baz 接收到的參數所占的記憶體大小 : ```diff= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<u32>); baz(bar::<i32>); } fn bar<T>(_: u32) -> u32 { 0 } -fn baz(f: fn(u32) -> u32 ) {} +fn baz(f: fn(u32) -> u32 ) { + println!("{}", std::mem::size_of_val(&f)); +} ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); } fn bar<T>(_: u32) -> u32 { 0 } fn baz(f: fn(u32) -> u32 ) { println!("{}", std::mem::size_of_val(&f)); } ``` ::: 編譯並執行可以看到編譯器真的將 function item 強制轉換成函式指標 : ```rust= $ cargo check ... Hello, world! 0 8 8 ``` 這個轉換是必要的,因為這兩個函式為不同的型別的 `bar` 函式 instantiations,這意味著它們具有不同的程式碼,它們已經被單獨最佳化了,它們是不同的程式碼塊,恰好共享一個泛用名稱。 當我們第一次呼叫 `baz` 函式時,我們需要將指標傳遞給函式的一個 instance 的第一個指令,而第二次呼叫 `baz` 函式時,則需要傳遞一個不同的函式指標。 `fn baz(f: fn(u32) -> u32 )` 確實允許我們對傳入的確切函式進行泛型化,只要它**具有相同的簽章**,若將 bar 的函式簽章做修改將會產生編譯錯誤 : ```diff= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); } -fn bar<T>(_: u32) -> u32 { 0 } +fn bar<T>() {} fn baz(f: fn(u32) -> u32 ) { println!("{}", std::mem::size_of_val(&f)); } ``` 編譯出現以下錯誤 : ```rust= $ cargo run ... error[E0308]: mismatched types --> src\main.rs:6:9 | 6 | baz(bar::<i32>); | --- ^^^^^^^^^^ incorrect number of function parameters | | | arguments to this function are incorrect | = note: expected fn pointer `fn(u32) -> u32` found fn item `fn() {bar::<i32>}` ... ``` 我們從中學到的是,function item 和函式指標彼此不同,但 function item 可以強制轉換為函式指標。編譯器這麼做的原因如下 : ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); } fn bar<T>(_: u32) -> u32 { 0 } fn baz(f: fn(u32) -> u32 ) { println!("{}", std::mem::size_of_val(&f)); } ``` 如果沒有在 Line 6 與 Line 7 呼叫 `baz` 函式,即使我們將具 `T` 的 `bar` 函式 instantiat 為 `i32`,編譯器實際上並不需要產生該函式的程式,它並沒有義務這樣做,因為 `bar::<i32>` 從未被呼叫。 如果在 Line 6 與 Line 7 呼叫 `baz` 函式,編譯器將此強制轉換為指標的原因是,它需要能夠產生一個指向該函式 body 的指標值。因此,編譯器必須產生該函式的程式碼,以便首先產生該函式指標。 結論 : Function item 唯一標識函式的特定 instance,而函式指標則是指向具有特定簽章的函式的指標,你可以將一個 function item 轉換為函式指標,但不能反向進行轉換。 :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } ``` ::: ## Function traits [0:11:24](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=684s) 接著看到 `Fn()` : ```rust= fn main() { ... quox(bar::<u32>); } fn bar<T>() {} ... fn quox<F>(f : F) where F: Fn(), { } ``` `Fn()` 與 `fn()` 是不一樣的東西,雖然它們的建構方式相同。`fn()` 作為函式指標使用,`Fn()` 則是 trait。 * [Trait std::ops::Fn](https://doc.rust-lang.org/std/ops/trait.Fn.html) 的簽章如下 : ```rust= pub trait Fn<Args>: FnMut<Args> where Args: Tuple, { // Required method extern "rust-call" fn call(&self, args: Args) -> Self::Output; // 傳入 self 的參考 } ``` `Fn()` 在某種程度上相當於擁有共享參考,因為你可以多次呼叫它,並且你可以透過共享參考同時多次呼叫它,或者至少是透過一個共享參考。 * [Trait std::ops::FnMut](https://doc.rust-lang.org/std/ops/trait.FnMut.html) 的簽章如下 : ```rust= pub trait FnMut<Args>: FnOnce<Args> where Args: Tuple, { // Required method extern "rust-call" fn call_mut( &mut self, args: Args ) -> Self::Output; // 傳入 self 的 exclusive 參考 } ``` `FnMut()` 的話,你一次只能呼叫一次。所以,首先你需要一個 mutable 參考。如果你有一個 mutable 參考,**你可以多次呼叫它,但一次只能呼叫一次**,這點很重要。例如,如果你將 `FnMut()` 插入 Rc,則將無法呼叫它。同樣,如果你提供了對 `FnMut()` 的共享參考,你也無法呼叫它。這也是為什麼一般情況下,如果你透過 [rayon](https://docs.rs/rayon/latest/rayon/) 之類的東西將它傳遞給多個執行緒,那麼你就不能同時為多個執行緒呼叫 `FnMut`,因為它不是 exclusive。 * [Trait std::ops::FnOnce](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) 的簽章如下 : ```rust= pub trait FnOnce<Args> where Args: Tuple, { type Output; // Required method extern "rust-call" fn call_once(self, args: Args) -> Self::Output; // 傳入 owned self } ``` 從 `FnOnce()` 的命名得知,你只能呼叫它一次。你已經移動了你想要呼叫的函式的值,你再也無法再次呼叫它。 一旦我們了解什麼是 closures 以及它們的作用,原因就會變得清晰。讓我們先具體考慮一下這對於 function item 和函式指標意味著什麼。 function item 和函式指標並沒有狀態,它們只是獨立的程式碼區塊,並且不參考任何其他 stack frame 中的任何內容。它們不參考儲存在自身外部的任何記憶體。它們不與生命週期相關聯,這意味著它們實際上不關心 `self`。對於 function item 來說,`self` 中不包含任何東西。對於函式指標來說,`self` 只是指向函式的指標。但實際上沒有狀態可變。沒有東西可移動。 因此,function item 強制轉換為函式指標,而函式指標實作了 `Fn`/ `FnMut`/ `FnOnce` trait。因此,如果你有一個函式指標,則可以將其傳遞給接受 `Fn`/ `FnMut`/ `FnOnce` 的函式。 這些 `Fn` trait 的一種思考方式是,它們是一種階層結構 : ![image](https://hackmd.io/_uploads/B1dy5DVi6.png) 對應文件 : > Since both `Fn` and `FnMut` are **subtraits** of `FnOnce`, any instance of `Fn` or `FnMut` can be used where a `FnOnce` is expected. 根據文件的敘述可以得知 `FnOnce` 實作了 `Fn` : ```rust= impl<F> FnOnce for F where F: Fn(), { fn call(self) {} } ``` 我們只談到了函式指標,但如果我們有一個對舊 owned `self` 的參考,或者一個 owned `self`,那麼我們可以輕而易舉地產生一個對 `self` 的參考。因此,我們可以輕鬆地將 `self` 作為 `Fn` 呼叫。就像我可以輕鬆地將一個轉換為另一個一樣。因為如果我有一個實作了 `Fn` 的東西,並且我有一個 `self`,我只需將其取為參考並將其傳遞給 `Fn` : ```rust= impl<F> FnOnce for F where F: Fn(), { fn call(self) { Fn::call(&self); } } ``` `FnOnce` 也可以實作出 `FnMut` : ```rust= impl<F> FnOnce for F where F: FnMut(), { fn call(mut self) { FnMut::call(&mut self); } } ``` `FnMut` 則可以實作出 `Fn` : ```rust= impl<F> FnMut for F where F: Fn(), { fn call(&mut self) { Fn::call(&*self); } } ``` 函式指標實作了 `Fn`/ `FnMut`/ `FnOnce` trait。因為它實際上不關心函式指標的所有權,因為它只是指向程式碼的指標。所以前面的 `quox` 函式的 bound 可以是 `Fn`/ `FnMut`/ `FnOnce` 其中一個。 * 如果是 quox 函式使用到 `FnOnce` bound,若傳入 exclusive 參考,你不能呼叫 `f`,因為你並沒有 `f` 的擁有權 : ```rust= fn main() { ... quox(&mut bar::<u32>); } fn quox<F>(f : &mut F) where F: FnOnce(), { (f)() } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&mut bar::<u32>); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &mut F) where F: FnOnce(), { (f)() } ``` ::: 編譯會出現以下錯誤,因為傳入的並不是 owned,只是 mutable 參考 : ```rust= $ cargo run ... error[E0507]: cannot move out of `*f` which is behind a mutable reference --> src\main.rs:29:5 | 29 | (f)() | ^^^-- | | | `*f` moved due to this call | move occurs because `*f` has type `F`, which does not implement the `Copy` trait ... ``` 如果你將 bound 改為 FnMut 就可以呼叫 `f` : ```rust= fn main() { ... quox(&mut bar::<u32>); } fn quox<F>(f : &mut F) where F: FnMut(), { (f)() } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&mut bar::<u32>); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &mut F) where F: FnMut(), { (f)() } ``` ::: * 如果是 quox 函式使用到 `FnMut` bound,若傳入共享參考,你不能呼叫 `f`,因為你並沒有 `f` 的 mutable 參考 : ```rust= fn main() { ... quox(&bar::<u32>); } fn quox<F>(f : &F) where F: FnMut(), { (f)() } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &F) where F: FnMut(), { (f)() } ``` ::: 編譯會出現以下錯誤,因為傳入的並不是 mutable 參考,只是共享參考 : ```rust= $ cargo run ... error[E0596]: cannot borrow `*f` as mutable, as it is behind a `&` reference --> src\main.rs:29:5 | 29 | (f)() | ^^^ `f` is a `&` reference, so the data it refers to cannot be borrowed as mutable ... ``` 如果你將 bound 改為 Fn 就可以呼叫 `f` : ```rust= fn main() { ... quox(&bar::<u32>); } fn quox<F>(f : &F) where F: Fn(), { (f)() } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &F) where F: Fn(), { (f)() } ``` ::: ## Closures [0:20:40](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=1240s) 為什麼我們需要 `Fn`/ `FnMut`/ `FnOnce` 這些差異 ? 一旦你開始談論 closure,差異就會出現。 首先看到 non-capturing closure : ```rust= fn main() { let f = |x: i32, y:i32| x + y; } ``` closures 之所以被稱為 closure 是因為它們封閉了它們的環境。它們能夠從其環境中 capture 東西,並生成一個唯一的函式。這些函式在創建時會具體吸收、使用或參考周圍環境中的資料。這個例子,它不會從環境中 capture 任何內容。它僅參考自己的參數。因此這是一個 non-capturing closure。 non-capturing closure 的有趣之處在於它們可以強制轉換為函式指標,並且可以有三種 trait 的 bound : ```rust= fn main() { ... let f = || (); baz(f); quox(&f); } ... fn quox<F>(f : &F) where F: Fn(), { (f)() } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); bazbar::<u32>); quox(&bar::<u32>); let f = || (); baz(f); quox(&f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &F) where F: Fn(), { (f)() } ``` ::: 如果是 `f` 是 capturing closure 就不行 : ```rust= fn main() { ... let z = String::new(); // 用 not Copy 的型別比較好說明 let f = || { let _ = z ; // 耗用 z }; baz(f); quox(&f); } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let z = String::new(); let f = || { let _ = z ; }; baz(f); quox(&f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &F) where F: Fn(), { (f)() } ``` ::: 編譯產生以下錯誤 : ```rust $ cargo run ... error[E0308]: mismatched types --> src\main.rs:13:9 | 10 | let f = || { | -- the found closure ... 13 | baz(f); | --- ^ expected fn pointer, found closure | | | arguments to this function are incorrect | = note: expected fn pointer `fn()` found closure `[closure@src\main.rs:10:13: 10:15]` note: closures can only be coerced to `fn` types if they do not capture any variables ... ``` 看到倒數第二行的說明,而我們程式的 `f` 真的有 capture 東西 (`z`)。 即便是只拿 `z` 的共享參考而不是移動 `z`,並將之列印出來也會產生相同編譯錯誤訊息 : ```rust= fn main() { ... let z = String::new(); let f = || { println!("{}", z); }; baz(f); quox(&f); } ``` `f` closure 仍會 capture 環境,這意味著你不能將 `f` closure 表示為函式指標。你可以將其想像成編譯器為 closure 產生了一種匿名結構,其中包含了從其環境中 capture 的所有欄位,當該 closure 執行時,它需要存取那些資訊 你可以把它想像成一個 `f` closure 結構,並且實作 `Fn` : ```rust= fn main() { ... let z = String::new(); let f = || { println!("{}", z); }; struct FClosure<'scope > { z: &'scope String, } impl<'scope> Fn() for FClosure<'scope> { fn call(&self) { // copy-paste from closure-definition println!("{}", self.z); } } baz(f); quox(&f); } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let z = String::new(); let f = || { println!("{}", z); }; // struct FClosure<'scope > // { // z: &'scope String, // } // impl<'scope> Fn() for FClosure<'scope> // { // fn call(&self) // { // // copy-paste from closure-definition // println!("{}", self.z); // } // } baz(f); quox(&f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &F) where F: Fn(), { (f)() } ``` ::: 你實際上無法編寫這段程式碼,但你可以將其視為編譯器產生的內容。因此你明白為什麼它需要存取 `z` 了。函式指標(只是指向此程式碼區塊的開頭的指標)是不夠的,**它需要 `z` 的附加狀態**。 將 `z` 改成 mut : ```rust= fn main() { ... let mut z = String::new(); let f = || { z.clear(); }; // struct FClosure<'scope > // { // z: &'scope mut String, // 需要 exclusive 參考 // } // impl<'scope> Fn() for FClosure<'scope> // { // fn call(&self) // { // // copy-paste from closure-definition // self.z.clear(); // & &mut String -> &String // // 一個對字串的 exclusive 參考的共享參考, // // 只能用作字串的共享參考。 // } // } baz(f); quox(&f); } ``` Line 16 無法運作,因為 `z` 要求 mutable 參考,因為我們假設現在是 `Fn`,而 `Fn` 擁有的只是 `&self`。 :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let mut z = String::new(); let f = || { z.clear(); }; // struct FClosure<'scope > // { // z: &'scope mut String, // } // impl<'scope> Fn() for FClosure<'scope> // { // fn call(&self) // { // // copy-paste from closure-definition // self.z.clear(); // } // } baz(f); quox(&f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &F) where F: Fn(), { (f)() } ``` ::: :::success :pencil2: [String::clear(&mut self)](https://doc.rust-lang.org/std/string/struct.String.html#method.clear) Truncates this String, removing all contents. While this means the String will have a length of zero, it **does not touch its capacity**. ::: 如果現在是 `FnMut`,那我們得到的就會是 mutable 參考 : ```rust= fn main() { ... let mut z = String::new(); let f = || { z.clear(); }; // struct FClosure<'scope > // { // z: &'scope mut String, // 需要 exclusive 參考 // } // // 任何實作了 FnMut 的東西都實作了 FnOnce, // // 因此這裡也有實作 FnOnce。 // impl<'scope> FnMut() for FClosure<'scope> // { // fn call(&mut self) // { // // copy-paste from closure-definition // self.z.clear(); // } // } baz(f); // 編譯器提示錯誤 : // expected fn pointer, found closure // 並沒有強制轉換成函式指標, // 因為 `f` capture 了環境。 quox(&f); // 編譯器提示錯誤 : // expected a closure that implements the `Fn` trait, // but this closure only implements `FnMut` // f 無法實作 Fn,因為它需要對其環境進行 mutably borrow。 } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let mut z = String::new(); let f = || { z.clear(); }; // struct FClosure<'scope > // { // z: &'scope mut String, // } // impl<'scope> FnMut() for FClosure<'scope> // { // fn call(&mut self) // { // // copy-paste from closure-definition // self.z.clear(); // } // } baz(f); quox(&f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f : &F) where F: Fn(), { (f)() } ``` ::: 將 `quox` 函式的 bound 改成 `FnMut()` : ```diff= fn main() { ... let mut z = String::new(); let f = || { z.clear(); }; - baz(f); + // baz(f); // 為了說明 quox,先將這行註解掉 - quox(&f); + quox(f); } ... -fn quox<F>(f : &F) +fn quox<F>(mut f: F) - where F: Fn(), + where F: FnMut(), { (f)() } ``` 由於 `f` 為 `FnMut`,且 quox 要求的 bound 又是 `FnMut`,這次就可以編譯成功了。 :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let mut z = String::new(); let f = || { z.clear(); }; // struct FClosure<'scope > // { // z: &'scope mut String, // } // impl<'scope> FnMut() for FClosure<'scope> // { // fn call(&mut self) // { // // copy-paste from closure-definition // self.z.clear(); // } // } // baz(f) quox(f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(mut f: F) where F: FnMut(), { (f)() } ``` ::: 接著探討 `FnOnce` : ```rust= fn main() { ... let mut z = String::new(); let f = || { drop(z); // 為了卸除 z 我們必須有 z 的擁有權 // 所以我們需要將 z 移動到 closure 內。 // 你只能移動 z 到這個 closure 一次 }; // struct FClosure<'scope > // { // z: String // 需要擁有字串 // } // impl<'scope> FnMut() for FClosure<'scope> // { // fn call(&mut self) // { // // copy-paste from closure-definition // drop(self.z); // // 不可能在這裡卸除 z, // // 因為我們只有 z 的 mutable 參考。 // } // } // baz(f); quox(f); } ``` 假設 closure 是 `FnOnce` 就沒有問題 (現在 closure 仍為 `FnMut`) : ```rust= fn main() { ... let mut z = String::new(); let f = || { drop(z); // 編譯器錯誤提示訊息 : // this closure implements `FnOnce`, not `FnMut` // // 為了卸除 z 我們必須有 z 的擁有權 // 所以我們需要將 z 移動到 closure 內。 // // 你只能移動 z 到這個 closure 一次 }; // struct FClosure<'scope > // { // z: String // 需要擁有字串 // } // impl<'scope> FnOnce() for FClosure<'scope> // { // fn call(self) // { // // copy-paste from closure-definition // drop(self.z); // // 可以在這裡卸除 z, // // 因為我們有 z 擁有權。 // } // } // baz(f); quox(f); } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let mut z = String::new(); let f = || { drop(z); }; // struct FClosure<'scope > // { // z: &'scope mut String, // } // impl<'scope> FnMut() for FClosure<'scope> // { // fn call(&mut self) // { // // copy-paste from closure-definition // self.z.clear(); // } // } // baz(f) quox(f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(mut f: F) where F: FnMut(), { (f)() } ``` ::: 使用 move 即可將值移入 closure : ```diff= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let mut z = String::new(); - let f = || { + let f = move || { println!("{}", z); + // z is dropped here }; // baz(f) quox(f); } ... -fn quox<F>(mut f: F) +fn quox<F>(f: F) - where F: FnMut(), + where F: FnOnce(), { (f)() } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let mut z = String::new(); let f = move || { drop(z); }; // baz(f) quox(f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f: F) where F: FnOnce(), { (f)() } ``` ::: ## Non-static closures [0:33:49](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=2029s) 如果你沒聲明 closure 的生命週期,編譯器預設它是 static : ```rust= fn make_fn() -> impl Fn() { let z = String::new(); || { println!("{}", z); // borrow z, // 無法編譯,因為 z 的生命週期是函式存活的時間。 } } ``` 若你將 `z` 值移動到 closure 內,就可以編譯 : ```rust= fn make_fn() -> impl Fn() { let z = String::new(); move || { println!("{}", z); // 這個 closure 現在擁有 z, // 且 z 的生命週期跟 closure 一樣。 } } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let mut z = String::new(); let f = move || { drop(z); }; // baz(f) quox(f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(f: F) where F: FnOnce(), { (f)() } pub fn make_fn() -> impl Fn() { let z = String::new(); move || { println!("{}", z); } } ``` ::: 嘗試在 closure 裡卸除 z : ```rust= fn make_fn() -> impl Fn() { let z = String::new(); move || { drop(z); // 編譯器提示錯誤 : // move occurs because `z` has type `String`, // which does not implement the `Copy` trait // 因為回傳型態是 Fn 而不是 FnOnce。 } } ``` 移動的一個缺點是會移動一切 closure 用到的東西 : ```rust= fn make_fn() -> impl Fn() { let x = String::new(); let z = String::new(); move || { println!("{}", x); println!("{}", z); } } ``` 你不總是想要移動一切,避免移動一切的作法有 : * 能不要用 `move` 就不要用 : ```rust= fn make_fn() -> impl Fn() { let x = String::new(); let z = String::new(); || { println!("{}", x); println!("{}", z); } } ``` * 利用間接的方式,也就是僅傳入參考 : ```rust= fn make_fn() -> impl Fn() { let x = String::new(); let z = String::new(); let x2 = &x; // 這個例子不會運作,因為 x2 生命週期並非 static, // 但這裡想表達的是你可以使用這種方法來避免移動一切。 move || { println!("{}", x2); println!("{}", z); } } ``` * 如果你不在意舊值,可以使用 shadowing,引進新的 scope : ```rust= // 這個例子不會運作,原因 : // 1. x 生命週期並非 static // 2. 回傳型態為 () // 但這裡想表達的是你可以使用這種方法來避免移動一切。 fn make_fn() -> impl Fn() { let x = String::new(); let z = String::new(); { let x = &x; // 為了只移動參考 move || { println!("{}", x2); println!("{}", z); } } } ``` Q : wait does that code create a new String in 'static, every time make_fn(I) is called? A : 是的,如下 : ```rust= pub fn make_fn() -> impl Fn() { let z = String::new(); // 建立新的 String move || { println!("{}", z); // closure 擁有 z // 當 closure 被卸除時,z 才會被卸除。 } } ``` ## dyn Fn [0:38:50](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=2330s) 接著看 trait 物件,`Fn`/ `FnMut`/ `FnOnce` 皆可被 dynamic dispatch : * `dyn Fn()` ```rust= fn hello(f: Box<dyn Fn()>) { f() } ``` * `dyn FnMut()` ```rust= fn hello(mut f: Box<dyn FnMut()>) { f() } ``` * `dyn FnOnce()` ```rust= fn hello(mut f: Box<dyn FnOnce()>) { f() } ``` 繼續看到 `Box<dyn Fn_>` : ```rust= // Box<dyn Fn()> did not implement Fn() // Box<dyn FnMut()> did not implement FnMut() // Box<dyn FnOnce()> did not implement FnOnce() ``` 上述的三種情況的原因很有趣,假設我們想實作 `Fn_` : ```rust= impl FnOnce() for Box<dyn FnOnce()> { fn call(self) { // self = Box, self.0 取 Box 裡面的值 let x: dyn FnOnce() = self.0; // x 的型別是 dyn FnOnce(), // 但 dyn FnOnce() 不是 Sized, // x 究竟占據 stack 記憶體多少空間 ? x.call() } } ``` :::success :pencil2: [Fn* closure traits implemented for Box<dyn Fn*>](https://blog.rust-lang.org/2019/05/23/Rust-1.35.0.html#fn-closure-traits-implemented-for-boxdyn-fn) This was ultimately due to a limitation in the compiler's ability to reason about such implementations, which has since been fixed with the introduction of [unsized locals](https://blog.rust-lang.org/2019/05/23/Rust-1.35.0.html#fn-closure-traits-implemented-for-boxdyn-fn). ::: ::: warning :question: [0:42:42](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=2562s) 繼續看到 `Fn_` 實作 : ```rust= impl FnOnce() for Box<dyn FnOnce()> { fn call(self) // 你可能會問,為什麼不拿 Box 內容的共享參考 ? { // 如果你要呼叫 x, // 你必須擁有 self,你不能只拿到 self 的共享參考 let x: &dyn FnOnce() = &self.0; x.call() } } ``` :::success :pencil2: [Tracking issue for RFC #1909: Unsized Rvalues (unsized_locals, unsized_fn_params) #48055](https://github.com/rust-lang/rust/issues/48055) 有一個專門針對 Unsized Rvalues 的 RFC,這是此實作存在所必需的。RFC 已經完成,但還有很多實作問題,基本上,編譯器可以利用這個特定的功能,但這是不穩定的。你可以在 nightly 版本中選擇使用它,但通常不能在穩定版本上使用這個功能。但這對於這個特定的功能是必要的,這是一個有趣的小插曲。 ::: 當你擁有一個 `Box<dyn Fn>` 這樣的型別時,現在它是有效的,你不需要特別對待它,一般來說,`dyn` 就能正常工作。 ::: 嘗試將 `f` 變成 `&dyn Fn()` 是可以的 : ```rust= fn main() { ... // baz(f) let f: &dyn Fn() = &f; // 這有點像函式指標,但它被允許有一個 self。 // 基本上它建構了一個用於這個 closure 的 cell, // 然後將其用作 dynamic dispatch 的資料指標, // 而 vtable 只有 call 方法。 quox(f); } ... fn quox<F>(mut f: F) where F: FnMut(), { (f)() } ``` :::spoiler {state="close"} 目前程式碼 ```rust= fn main() { println!("Hello, world!"); let mut x = bar::<i32>; println!("{}", std::mem::size_of_val(&x)); // x = bar::<u32>; baz(bar::<i32>); baz(bar::<u32>); quox(&bar::<u32>); let mut z = String::new(); let f = || { println!("{}", z); }; // baz(f) let f: &dyn Fn() = &f; quox(f); } fn bar<T>() {} fn baz(f: fn()) { println!("{}", std::mem::size_of_val(&f)); } fn quox<F>(mut f: F) where F: FnMut(), { (f)() } pub fn make_fn() -> impl Fn() { let z = String::new(); move || { println!("{}", z); } } ``` ::: 但若將 `f` 變成 `&dyn FnMut()` 就不可以,原因如下 : ```rust= fn main() { ... // baz(f) // 現在只有 f 的共享參考, // 所以 &dyn FnMut() 並未實作 FnMut,只實作 Fn。 let f: &dyn FnMut() = &f; quox(f); } ... fn quox<F>(mut f: F) where F: FnMut(), { (f)() } ``` 當你使用 dynamic dispatch 的 `Fn_` trait 時,你需要確保所使用的 wrapper、間接型別、寬指標型別允許你實際上需要的存取方式,以便呼叫該函式 : ```rust= let f: &mut dyn FnMut() = &f; let f: &dyn FnMut() = &f; let f: Box<dyn FnOnce()> = Box::new(f); // ok,因為 FnOnce 的限制比 FnMut 還嚴格。 // let f: Box<dyn FnMut()> = Box::new(f); // ok,因為 FnOnce 的限制比 Fn 還嚴格。 // let f: Box<dyn Fn()> = Box::new(f); // Arc 尚未支援 !!! // let f: std::sync::Arc<dyn Fn()> = std::sync::Arc::new(f); // Arc 允許你將 Unsized 的事物放入其中, // 但是出於必要,Arc 只會給你對內部事物的共享存取權限, // 因此 Arc<dyn Fn()> 實作了 Fn, // 但是 Arc<dyn FnMut()> 仍然只實作了 Fn,無法實作 FnMut。 ``` :::info :bulb: Arc 尚未支援 `dyn Fn()` !!! 看到文件的 [Implementors 部分](https://doc.rust-lang.org/std/ops/trait.Fn.html#implementors) : ```rust= impl<Args, F, A> Fn<Args> for Box<F, A> ``` 分析未支援 Arc 的原因 : Arc 支援 Unsized 的值。Arc 可以支持作為寬指標,因此它可以容納 `dyn Fn()`,如果它可以容納 `dyn Fn()`,並且能夠給你 closure state 的共享參考,那麼它應該能夠實作 `Fn`,因為這只需要能夠獲取 closure state 的共享參考。這表明這裡可能存在某種實作缺失,這可能是由於我們正在查看的關於 Unsized Rvalues 的問題有關,也許 Unsized Rvalues 被專門用來處理 `Box`。 ::: ## const Fn [0:49:44](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=2984s) 接著看到 `const Fn` : ```rust= fn main() { let x = || 0; // constant closure // 你可以在編譯時期求值這個 closure foo(x); } const fn make_zero() -> i32 // 類似 Line 3 { 0 } const fn foo<F: FnOnce()>(f: F) { f() // 目前不行, // 因為編譯器不知道 f 可以當 constant 呼叫。 } ``` 今天在穩定版本中,Line 15 的問題,目前無能為力。但有一個有趣的 pre-RFC 討論一直在進行,討論關於是否有一種方式可以說,我想要對任何實作此 trait 的型別進行泛型化,但僅使用可 constant 求值的事物。 Jon 目前想到的語法是 (1.61.0 之前可以編譯) : ```diff= +#![feature(const_trait_impl, const_fn_trait_bound)] fn main() { let x = || 0; foo(x); } const fn make_zero() -> i32 // 類似 Line 3 { 0 } -const fn foo<F: FnOnce()>(f: F) +const fn foo<F: ~const FnOnce()>(f: F) { f() } ``` * `const` : `FnOnce()` 必須為 constant。 * `~const` : 如果 `FnOnce()` 是 constant,`foo()` 才會是 constant,反之非然。 * `?const` : 表 `FnOnce()` 可能是或不是 constant。 ```rust= $ rustup override set nightly $ cargo run ``` 比較主程式與非主程式傳入的 closure 皆為非 const 的情形 : ```rust= #![feature(const_trait_impl, const_fn_trait_bound)] fn main() { let x = || { String::from("new"); // x 是 !const -> foo 不是 const }; foo(x); // main 不是 const,所以沒要求 foo 是 const, // 所以 main 可以呼叫 foo。 } const fn test_foo() -> i32 { let x = || { String::from("new"); // x 是 !const -> foo 不是 const }; foo(x); // 在非 main 的地方呼叫 foo,要求 foo 是 const, // 所以 test_foo 不可以呼叫 foo。 } // ~const FnOnce() 在 Jon 測的當下,功能尚未穩定, // 所以 Line 14 ~ Line 24 Jon 沒有測出他想要展示的東西。 +const fn foo<F: ~const FnOnce()>(f: F) { f() } ``` ## for bounds [1:00:28](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=3628s) Q : Sometimes you end up with complicated lifetime bounds with for are. A : 如下 : ```rust= fn main() { quox(|x| x); } fn quox<F>(f: F) where F: Fn(&str) -> &str { } ``` 上面程式碼如預期運作,但有些情況下會變得更加複雜。比如在 bound 中,通常(雖然不總是)必須指定生命周期。在這裡,生命周期是什麼?通常情況下,當某物回傳時,你通常可以省略生命周期,但如果我們要嘗試指定這裡的生命周期,它們是什麼?因為這裡沒有生命周期。如果我們嘗試為這個 trait bound 填入生命周期,實際上是什麼呢? Line 5 沒有標記 `'a`,因為我們不能在這裡指派 `'a`。我們真正想說的是,我們希望 `F` 有生命周期,在輸出中重用其得到的生命周期。我們想說的是,它被允許參考與其輸入中參考的相同的東西。 特殊的 for 語法 : ```rust= fn main() { quox(|x| x); } fn quox<F>(f: F) where F: for<'a>Fn(&'a str) -> &'a str { } ``` `F: for<'a>Fn(&'a str) -> &'a str` 的實際意義是對 `F: Fn(&str) -> &str` 的 de-sugar。你可以把這個理解為對於任何生命週期標記 `'a`,`F` 是一個從生命週期 `'a` 的 `&str` 到另一個具有相同生命週期 `'a` 的 `&str` 的函式實作,所以其實並不那麼複雜。 這是一種方式說明它需要對於任何生命週期都成立,這才是應該有的 bound 。實際上,你幾乎不需要給出這樣的 `for` bound,但有時如果你的 trait bound 具有生命週期但不是 `Fn`,則可能會發生這種情況。對於任何 `Fn`、`FnMut` 或 `FnOnce` trait 的東西,編譯器通常很擅長推斷這一點,但一旦你在這裡開始擁有其他不是 `Fn` 的 traits,有時就需要使用 `for`,但這應該是非常非常罕見的。 ## closures in async fn [1:04:06](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=3846s) :::warning :question: [1:04:06](https://www.youtube.com/watch?v=dHkzSZnYXmk&t=3846s) Q : If I want to pass a closure to async function the clousure needs to be 'static right? How does this kind of closure capture it's enviroment? A : 如果你想將 closure 傳給 `async fn`,你可以這樣做,沒有什麼能阻止你接受任何函式 : ```rust= async fn quox<F>(f: F) where F: for<'a>Fn(&'a str) -> &'a str { } ``` 它不需要是 static,更多的是通常與 `Future` 一起使用,特別是如果你想要像 `tokio::spawn` 這樣的東西 : ```rust= fn main() { tokio::spawn(quox(|x| x)); // 像 thread::spawn 一樣要求參數是 static } // 如果這裡的 F 不是 static, // 回傳的 Future 也不會是 static。 async fn quox<F>(f: F) where F: for<'a>Fn(&'a str) -> &'a str { } // de-sugar Line 9 - Line 14 // fn quox<F>(f: F) -> impl Future<Output = ()> // where // F: for<'a>Fn(&'a str) -> &'a str // { // async move { // let _ = f; // } // } ``` `impl trait` 就像 `async fn` 一樣,自動 capture 其輸入的生命週期。因此,如果輸入與某個生命週期相關聯,則輸出型別也將與該相同的生命週期相關聯,這意味著它將不是 static,除非輸入是 static。這就是為什麼你通常需要將 static 添加到傳遞給 `async` 函式的泛型中的原因,這不是因為它們是必需的,如以下例子 : ```rust= fn main() { quox(|x| x).await; // 如果在這裡 await,那麼回傳的 Future 不需要是 static。 // 只有在嘗試進行類似於 spawning 的操作時, // 才需要將 Future 的生命週期延長到當前的 stack frame 之外 } ``` ::: Q : you often need to pin it, tho A : 你應該很少需要手動 pin 東西,一般來說,`await` 語法應該就會處理它。 ## 待整理 1. 0:42:42 2. 1:04:06