文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
5.2 闭包
闭包(closure, lambda)是 匿名函数 和其持有的 环境变量 组合。
- 参数和返回值类型可推导。
- 必须指定参数名。
- 参数以双竖线包含。
- 函数体大括号可选。
fn main() { // let f = |x| x + 1; // let f = |x: i32| x + 1; let f = |x: i32| -> i32 { x + 1 }; assert_eq!(f(2), 3); }
fn main() { let f = |x| x; f(2); // 通过此次调用推断类型。 f("abc"); ^^^^^ expected integer, found `&str` }
可直接执行。
fn main() { let x = (|n| { // 注意添加括号。 println!("hello"); n + 1 })(100); assert_eq!(x, 101); }
实现
环境变量是匿名函数定义所在上下文,非调用处。
struct closure-<id> { &var1, ... }
每个闭包变量都有唯一且匿名的类型,这导致无法在代码中声明。从实现上看,闭包结构仅保存环境变量引用,不包括函数。得益于编译器强大分析能力,直接对匿名函数静态调用。
fn main() { let v1 = 100; let v2 = 100; let a = |x: i32| x; // struct main::closure-0 let b = |x: i32| x + v1; // struct main::closure-1 let c = |x: i32| x + v1 + v2; // struct main::closure-2 assert_eq!(size_of(&a), 0); assert_eq!(size_of(&b), 8); // v1_ptr assert_eq!(size_of(&c), 16); // v1_ptr + v2_ptr }
反汇编查看具体实现和调用方式。
该实现不代表所有情形,作为返回值的闭包可能会复制栈帧内分配的局部环境变量。
fn main() { let a: i64 = 0x11; let b: i64 = 0x22; let f = |x: i64| x + a + b; f(0x33); }
(gdb) b 6 (gdb) r (gdb) ptype f # 查看闭包类型。 type = struct demo::main::closure-0 ( *mut i64, *mut i64, ) (gdb) p f # 查看闭包内容。 $1 = demo::main::closure-0 ( 0x7fffffffe1a0, 0x7fffffffe1a8 ) (gdb) p &a # 环境变量地址。 $2 = (*mut i64) 0x7fffffffe1a0 (gdb) p &b $3 = (*mut i64) 0x7fffffffe1a8
(gdb) disass Dump of assembler code for function demo::main: 0x0000555555559160 <+0>: sub rsp,0x28 ; 环境变量 a、b。 0x0000555555559164 <+4>: mov QWORD PTR [rsp],0x11 0x000055555555916c <+12>: mov QWORD PTR [rsp+0x8],0x22 ; closure { &a, &b } 0x0000555555559175 <+21>: mov rax,rsp 0x0000555555559178 <+24>: mov QWORD PTR [rsp+0x10],rax 0x000055555555917d <+29>: lea rax,[rsp+0x8] 0x0000555555559182 <+34>: mov QWORD PTR [rsp+0x18],rax ; call (rdi: &closure, rsi: 0x33) => 0x0000555555559187 <+39>: mov QWORD PTR [rsp+0x20],0x33 0x0000555555559190 <+48>: mov rsi,QWORD PTR [rsp+0x20] 0x0000555555559195 <+53>: lea rdi,[rsp+0x10] 0x000055555555919a <+58>: call 0x555555559270 <main::{{closure}}> 0x000055555555919f <+63>: add rsp,0x28 0x00005555555591a3 <+67>: ret End of assembler dump. (gdb) disass 0x555555559270 Dump of assembler code for function demo::main::{{closure}}: 0x0000555555559270 <+0>: sub rsp,0x28 ; 从 rdi、rsi 获取闭包内容和参数。 0x0000555555559274 <+4>: mov QWORD PTR [rsp+0x18],rdi 0x0000555555559279 <+9>: mov QWORD PTR [rsp+0x20],rsi
捕获
编译器尽可能按 &T -> &mut T -> move
顺序捕获环境变量,最大限度减少影响。
fn main() { let s = "abc".to_string(); // 所有权转移。 // fn test(s: String) { println!("{:?}", s); } // test(s); // 最小捕获,&T。 let c = || println!("{:?}", s); c(); assert_eq!(s.as_str(), "abc"); }
如要修改环境变量,则要求闭包也是可变的。
fn main() { let mut s = "abc".to_string(); // &mut T,闭包变量必须是 mut。 let mut c = || s.push('d'); c(); assert_eq!(s.as_str(), "abcd"); }
当需要所有权时,转移(move)所有权给闭包对象(非匿名函数)。
fn main() { let s = "abc".to_string(); // 匿名函数内需要所有权转移。 // 闭包创建时,所有权属于闭包对象,而非匿名函数。 let c = || { let _s = s; }; -- - variable moved due to use in closure | value moved into closure here println!("{:?}", s); ^ value borrowed here after move c(); }
所有权转移,须注意多次调用引发的释放(drop)问题。
fn main() { let s = "abc".to_string(); // 第一次执行,闭包将所有权转移给 _s,本次调用结束时释放。 let c = || { let _s = s; }; c(); // --- `c` moved due to this call // c(); // ^ value used here after move }
(gdb) disass Dump of assembler code for function demo::main: 0x0000555555559208 <+24>: call 0x55555555aac0 <alloc...to_string> ... 0x0000555555559249 <+89>: lea rdi,[rsp+0x38] 0x000055555555924e <+94>: call 0x5555555599b0 <main::{{closure}}> ... End of assembler dump. (gdb) disass 0x5555555599b0 Dump of assembler code for function demo::main::{{closure}}: 0x00005555555599cd <+29>: mov rdi,rsp 0x00005555555599d0 <+32>: call 0x55555555ac80 <drop_in_place<String>> 0x00005555555599d5 <+37>: add rsp,0x18 0x00005555555599d9 <+41>: ret End of assembler dump.
还可以用 move
关键字强制转移所有权,无论闭包是否需要。
fn main() { let s = "abc".to_string(); // 强制转移所有权。 let c = move || println!("{:?}", s); // 注意!所有权被闭包持有,而非匿名函数。 // 匿名函数内部仅引用(&T),故不会释放。 c(); c(); }
(gdb) b 9 (gdb) r (gdb) disass/s 1 fn main() { 2 let s = "abc".to_string(); 5 let c = move || println!("{:?}", s); 6 7 // 注意!所有权被闭包持有,而非匿名函数。 8 // 匿名函数内部仅引用(&T),故不会释放。 9 c(); => 0x0000555555568a73 <+51>: call 0x555555568af0 <closure#0> 10 c(); 0x0000555555568a9b <+91>: call 0x555555568af0 <closure#0> 11 } 0x0000555555568aa7 <+103>: call 0x555555569870 <drop_in_place<closure_env#0>> 0x0000555555568ab0 <+112>: ret
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论