owned this note
owned this note
Published
Linked with GitHub
---
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 的一種思考方式是,它們是一種階層結構 :

對應文件 :
> 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