Rust Closure
Table of Contents
1. Rust Closure
1.1. function
function 是 first class object, 当函数作为参数, 返回值, 变量等情况下, 实际上使用的是函数指针, 其类型为 fn(args)->(return).
fn test() {} fn call(f: fn()) { println!("call:"); println!("f is at {:?}", &f as *const _); println!("which point to {:?}", f as *const ()); unsafe { println!("also 0x{:x}", *(&f as *const _ as *const i64)); } f(); } fn main() { println!("main:"); println!("test is at {:?}", test as *const ()); call(test); }
main: test is at 0x55e7143f37f0 call: f is at 0x7ffc23659470 which point to 0x55e7143f37f0 also 0x55e7143f37f0
1.2. closure
1.2.1. memory layout
closure 分为两种情况:
- 当 closure 不需要 capture 时, 和普通函数相同.
- 当 closure 需要 capture 时, 需要占用额外的空间保存 capture 的数据
fn main() { println!("\nmove ---------------"); let x = 0xffi64; println!("x is at {:?}", &x as *const _); // move let foo_move = move || { println!("{:?}", x); }; println!("size of foo: {}", std::mem::size_of_val(&foo_move)); println!("foo_move is at {:?}", &foo_move as *const _); unsafe { println!( "foo_move val is 0x{:x}", *(&foo_move as *const _ as *const i64) ); } // borrow println!("\nborrow ---------------"); let foo_borrow = || { println!("{:?}", x); }; println!("size of foo_borrow: {}", std::mem::size_of_val(&foo_borrow)); println!("foo_borrow is at {:?}", &foo_borrow as *const _); unsafe { println!( "foo_borrow val is {:x}, whic is &x", *(&foo_borrow as *const _ as *const i64) ); } }
move --------------- x is at 0x7ffc6e4c7d08 size of foo: 8 foo_move is at 0x7ffc6e4c7d68 foo_move val is 0xff borrow --------------- size of foo_borrow: 8 foo_borrow is at 0x7ffc6e4c7ea0 foo_borrow val is 7ffc6e4c7d08, whic is &x
closure 对应的 code 并不需要包含在 closure 中, 因为每个 closure 都是不同的类型, 编译器知道每个 closure 对应的 code 在哪里.
1.2.2. closure 的类型
#![feature(core_intrinsics)] fn type_of<T>(_: &T) -> &str { unsafe { std::intrinsics::type_name::<T>() } } fn test() -> () {} fn main() { let foo = || {}; let bar = || {}; println!("type of foo: {:?}", type_of(&foo)); println!("type of bar: {:?}", type_of(&bar)); println!("type of test: {:?}", type_of(&test)); }
type of foo: "d41d8cd98f::main::{{closure}}" type of bar: "d41d8cd98f::main::{{closure}}" type of test: "d41d8cd98f::test"
1.3. Fn trait
closure 无法做为函数的参数和返回值等, 因为:
- closure 都是动态生成的类型, 无法写在函数的 signature 里, 只能依靠编译器的类型推导 (c++ 的 auto 关键字解决类似的问题)
- closure 的大小是不确定的, 同样 signature 的 closure, 会因为 capture 的不同导致不同的大小, 所以无法通过类似 `fn()->()` 来指示 closure 的类型
fn call(f: fn()) { f(); } fn test() {} fn main() { // OK, 没有 capture 的 closure 可以 coerce 为 fn() call(|| { println!("hello"); }); // OK call(test); // WRONG, 需要 capture 的 closure 无法 coerce 为 fn() // error[E0308]: mismatched types // --> src/main.rs:19:10 // | // 19 | call(|| { // | __________^ // 20 | | println!("{:?}", x); // 21 | | }) // | |_____^ expected fn pointer, found closure // | // = note: expected type `fn()` let x = 1; call(|| { println!("{:?}", x); }) }
解决方法是使用 Fn trait, 因为 closure, fn 都会 impl Fn trait
fn call(f: &dyn Fn()) { f(); } fn test() { println!("normal fn"); } fn main() { call(&test); call(&|| { println!("uncaptured closure"); }); let x = 1; call(&|| { println!("captured closure: {:?}", x); }) }
normal fn uncaptured closure captured closure: 1
1.4. move
fn call(f: impl Fn()) { f() } fn main() { let f = { let x = "hello".to_owned(); let ret = move || { println!("{:?}", x); }; // error[E0382]: borrow of moved value: `x` // println!("{:?}", x); ret }; call(f) }
hello
默认情况下会使用 borrow 的方式 capture 环境变量, 若像上面的例子一样无法 borrow, 则需要显式的指定 move.
另外, move 是定义 closure 时 (而非执行时) 把环境变量一次性的 move 到 closure 内的. 后面执行 closure 时不会再发生 move
另外, 若 closure 代码会 consume 环境变量, 编译器会自动的 move
fn call(f: impl FnOnce()) { f() } fn main() { let x = "hello".to_owned(); let f = || { let y = x; println!("{:?}", y); }; println!("{:?}", x); call(f) } // error[E0382]: borrow of moved value: `x` // --> quickrun:11:22 // | // 6 | let x = "hello".to_owned(); // | - move occurs because `x` has type `std::string::String`, which does not implement the `Copy` trait // 7 | let f = || { // | -- value moved into closure here // 8 | let y = x; // | - variable moved due to use in closure // ... // 11 | println!("{:?}", x); // | ^ value borrowed here after move // // error: aborting due to previous error // // For more information about this error, try `rustc --explain E0382`.
1.5. Fn, FnMut, FnOnce
所有的 function 都 impl Fn.
但对于 closure 来说, 针对 capture 的不同, 编译器会自动 impl Fn, FnMut 或 FnOnce 中的一个.
1.5.1. FnOnce
move || { let z = x; };
这个 closure impl FnOnce, 因为 closure 每次执行时都会 move capture 到的变量, 但这个 move 只能进行一次. 所以 FnOnce 表示 closure 只能执行一次.
只有 move closure 才可能是 FnOnce
1.5.2. FnMut
若 closure 会修改 capture variable, 则它会 impl FnMut
let x = 1; || { x += 1; }
上面的 closure 实际会持有 `&mut x`
1.5.3. Fn
Fn 表示 closure 不会修改 capture variable 且可以执行多次.
1.5.4. trait bound
Fn 等作为 trait bound 时有不同的意义:
- Fn trait bound 表示需要能执行多次且不修改 capture variable
- FnMut trait bound 表示需要能执行多次且`可以`修改 (而不是一定要修改)
- FnOnce trait bound 表示不需要能执行多次
所以, Fn -> FnMut -> FnOnce 的要求是依次降低的:
- Fn trait bound 无法接受 FnMut 和 FnOnce, 因为它要求能执行多次且不修改
- FnMut trait bound 无法接受 FnOnce
- FnOnce trait bound 可以接受任何 Fn, FnMut 或 FnOnce