Rust闭包的虫洞穿梭

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1. 闭包是什么"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"闭包(Closure)的概念由来已久。无论哪种语言,闭包的概念都被以下几个特征共同约束:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"匿名函数"},{"type":"text","text":"(非独有,函数指针也可以);"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"可以调用闭包,并显式传递参数"},{"type":"text","text":"(非独有,函数指针也可以);"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"以变量形式存在,可以传来传去"},{"type":"text","text":"(非独有,函数指针也可以);"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"可以在闭包内直接捕获并使用定义所处作用域的值(独有)"},{"type":"text","text":";"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"神奇的是最后一点,理解起来也比较别扭的,习惯就好了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了说明上述特征,可以看一个Rust例子。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"fn display(age: u32, print_info: T)\n where T: Fn(u32)\n{\n print_info(age);\n}\n\nfn main() {\n let name = String::from(\"Ethan\");\n\n let print_info_closure = |age|{\n println!(\"name is {}\", name);\n println!(\"age is {}\", age);\n };\n\n let age = 18;\n display(age, print_info_closure);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行代码:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"name is Ethan"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"age is 18"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,闭包作为匿名函数存在了"},{"type":"codeinline","content":[{"type":"text","text":"print_info_closure"}]},{"type":"text","text":"栈变量中,然后传递给了函数"},{"type":"codeinline","content":[{"type":"text","text":"display"}]},{"type":"text","text":"作为参数,在"},{"type":"codeinline","content":[{"type":"text","text":"display"}]},{"type":"text","text":"内部调用了闭包,并传递了参数"},{"type":"codeinline","content":[{"type":"text","text":"age"}]},{"type":"text","text":"。最后神奇的事情出现了:在函数"},{"type":"codeinline","content":[{"type":"text","text":"display"}]},{"type":"text","text":"中调用的闭包居然打印出了函数"},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":"作用域中的变量"},{"type":"codeinline","content":[{"type":"text","text":"name"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b5/b5d5b71e8b41eec3249e9515b26df9e2.jpeg","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"闭包的精髓,就在于它同时涉及两个作用域"},{"type":"text","text":",就仿佛打开了一个\"虫洞\",让不同作用域的变量穿梭其中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"let x_closure = ||{};"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"单独一行代码,就藏着这个奥妙:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"赋值"},{"type":"codeinline","content":[{"type":"text","text":"="}]},{"type":"text","text":"的左侧,是存储闭包的变量,它处在一个作用域中,也就是我们说的闭包"},{"type":"text","marks":[{"type":"strong"}],"text":"定义处"},{"type":"text","text":"的环境上下文;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"赋值"},{"type":"codeinline","content":[{"type":"text","text":"="}]},{"type":"text","text":"的右侧,那对花括号"},{"type":"codeinline","content":[{"type":"text","text":"{}"}]},{"type":"text","text":"里,也是一个作用域,它在闭包"},{"type":"text","marks":[{"type":"strong"}],"text":"被调用处"},{"type":"text","text":"动态产生;"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"无论左侧右侧,都定义了闭包的属性,天然的联通了两个作用域。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于闭包,Rust如此,其他语言也大抵如此。不过,Rust不是还有所有权、生命周期这一档子事儿么,所以还可以深入分析下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2. Rust闭包捕获上下文的方式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如本篇题目,Rust闭包如何捕获上下文?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"换个问法,"},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":"作用域中的变量"},{"type":"codeinline","content":[{"type":"text","text":"name"}]},{"type":"text","text":"是以何种方式进入闭包的作用域的(第1节例子)?转移or借用?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"It Depends,视情况而定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Rust在std中定义了3种trait:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"FnOnce:闭包内对外部变量存在转移操作,导致外部变量不可用(所以只能call一次)"},{"type":"text","text":";"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"FnMut:闭包内对外部变量直接使用,并进行修改"},{"type":"text","text":";"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Fn:闭包内对外部变量直接使用,不进行修改"},{"type":"text","text":";"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"后者能办到的,前者一定能办到。反之则不然。所以,编译器对闭包签名进行推理时:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现FnMut的,同时也实现了FnOnce;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现Fn的,同时也实现了FnMut和FnOnce。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第1节的例子,将"},{"type":"codeinline","content":[{"type":"text","text":"display"}]},{"type":"text","text":"的泛型参数从Fn改成FnMut,也可以无警告通过。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"fn display(age: u32, mut print_info: T)\n where T: FnMut(u32)\n{\n print_info(age);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对环境变量进行捕获的闭包,需要额外的空间支持才能将环境变量进行存储。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3. 作为参数的闭包签名"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面代码"},{"type":"codeinline","content":[{"type":"text","text":"display"}]},{"type":"text","text":"函数定义,要接受一个闭包作为参数,揭示了如何显式的描述闭包的签名:在泛型参数上添加trait约束,比如"},{"type":"codeinline","content":[{"type":"text","text":"T: FnMut(u32)"}]},{"type":"text","text":",其中"},{"type":"codeinline","content":[{"type":"text","text":"(u32)"}]},{"type":"text","text":"显式的表示了输入参数的类型。"},{"type":"text","marks":[{"type":"strong"}],"text":"尽管是泛型参数约束,但是函数签名(除了没有函数名)描述还是非常精确的"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顺便说一句,Rust的泛型真的是干了不少事情,除了泛型该干的,还能添加trait约束,还能描述生命周期。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"描述签名是一回事,但是谁来定义闭包的签名呢?闭包定义处,我们没有看到任何的类型约束,直接就可以调用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案是:"},{"type":"text","marks":[{"type":"strong"}],"text":"闭包的签名,编译器全部一手包办了,它会将首次调用闭包传入参数和返回值的类型,绑定到闭包的签名"},{"type":"text","text":"。这就意味着,一旦闭包被调用过一次后,再次调用闭包时传入的参数类型,就必须是和第一次相同。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"传入参数和返回值类型绑定好了,但你心中难免还会有一丝忧愁:描述生命周期的泛型参数肿么办?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Rust编译器也搞得定。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"fn main(){\n\t\tlet lifttime_closure = |a, b|{\n println!(\"{}\", a);\n println!(\"{}\", b);\n b\n };\n let a = String::from(\"abc\");\n let c;\n {\n let b = String::from(\"xyz\");\n c = lifttime_closure(&a, &b);\n }\n println!(\"{}\", c);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上代码无法通过编译,成功检测出了悬垂引用:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"error[E0597]: "},{"type":"codeinline","content":[{"type":"text","text":"b"}]},{"type":"text","text":" does not live long enough"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"显然,对于闭包,编译期可以对引用的生命周期进行检查,以保证引用始终有效。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个例子,与其解释闭包与函数的区别,不如解释匿名函数与具名函数的区别:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具名函数是签名在先的,对于编译器来说,调用方和函数内部实现,只要分别遵守签名的约定即可。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"匿名函数的签名则是被推理出来的,编译器要"},{"type":"text","marks":[{"type":"strong"}],"text":"看全看透"},{"type":"text","text":"调用方的实际输入,以及函数内部的实际返回,检查自然也就顺带做掉了。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4. 函数返回闭包"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第1节的例子,我们将一个闭包作为函数参数传入,那么根据闭包的特性,它应该能够作为函数的返回值。答案是肯定的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基于前面介绍的Fn trait,我们定义一个返回闭包的函数,代码如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"fn closure_return() -> Fn() -> (){\n\t||{} \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可是,编译失败了:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"error[E0746]: return type cannot have an unboxed trait object"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"doesn't have a size known at compile-time"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"失败信息显示,编译器无法确定函数返回值的大小。一个闭包有多大呢?并不重要。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"开门见山,通用的解决方法是:为了能够返回闭包,可以使用一次装箱,从而将栈内存变量装箱存入堆内存,这样无论闭包有多大,函数返回值都是一个确定大小的指针。下面的代码里,使用"},{"type":"codeinline","content":[{"type":"text","text":"Box::new"}]},{"type":"text","text":"即可完成装箱。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"fn closure_inside() -> Box ()>\n{\n let mut age = 1;\n let mut name = String::from(\"Ethan\");\n\n let age_closure = move || {\n name.push_str(\" Yuan\");\n age += 1;\n println!(\"name is {}\", name);\n println!(\"age is {}\", age);\n };\n\n Box::new(age_closure)\n}\n\nfn main(){\n let mut age_closure = closure_inside();\n age_closure();\n age_closure();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行结果如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"name is Ethan Yuan"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"age is 2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"name is Ethan Yuan Yuan"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"age is 3"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的代码,除了让函数成功返回闭包之外,还有一个目的,我们想让闭包捕获函数内部环境中的值,但这次有些不同:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第1节代码示例,我们把外层的环境上下文,通过将闭包传入内层函数,这个不难理解,因为外层变量的生命周期更长,内层函数访问时,外层变量还活着;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而本节代码所做的,是通过闭包将内层函数的环境变量传出来给外层环境;"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"内层函数调用完成后就会销毁内层环境变量,那如何做到呢?幸好,Rust有"},{"type":"text","marks":[{"type":"strong"}],"text":"所有权转移"},{"type":"text","text":"。只要能促成内层函数的环境变量向闭包进行所有权的转移,这个操作顺理成章。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正因为Rust具有所有权转移的概念,返回闭包(同时捕获环境变量)的机理,Rust的要比任何具有垃圾回收语言(JavaScript、Java、C#)的解释都更简单明了。后者总会给人一丝不安:内部函数调用都结束了,居然局部变量还活着。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代码中的所有权转移,这里使用了关键字"},{"type":"codeinline","content":[{"type":"text","text":"move"}]},{"type":"text","text":",它可以在构建闭包时,强制将要捕获变量的所有权转移至闭包内部的特别存储区。需要注意的是,使用"},{"type":"codeinline","content":[{"type":"text","text":"move"}]},{"type":"text","text":",并不影响闭包的"},{"type":"codeinline","content":[{"type":"text","text":"trait"}]},{"type":"text","text":",本例中可以看到闭包是"},{"type":"codeinline","content":[{"type":"text","text":"FnMut"}]},{"type":"text","text":",而不是"},{"type":"codeinline","content":[{"type":"text","text":"FnOnce"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章