Rust学习笔记(三十八)闭包(上)
什么是闭包
可以捕获其所在环境的匿名函数叫做闭包。有4个特点:是匿名函数可以将其保存为变量并作为函数的参数可以在一个地方创建闭包,然后在另一个上下文中调用闭包来完成运算可以从其定义的作用域捕获值例子:生成自定义运动计划的程序
本例算法逻辑并不是重点,重点是算法中的计算过程需要几秒钟的时间。而我们的目标是不发生不必要的等待。即仅在必要时调用算法,而且只调用一次。srcmain。rsusestd::{thread,time::Duration};fnmain(){letsumulateduserspecifiedvalue10;letsimulatedrandomnumber7;generateworkout(sumulateduserspecifiedvalue,simulatedrandomnumber);}fnsimulatedexpensivecalculation(intensity:u32)u32{println!(calculatingslowly。。。);thread::sleep(Duration::fromsecs(2));sleep模拟等待时间intensity}fngenerateworkout(intensity:u32,randomnumber:u32){ifintensity25{运动强度小于25时,调用两次耗时算法生成两个运动计划println!(Today,do{}pushups!,simulatedexpensivecalculation(intensity));做一定时间的俯卧撑println!(Next,do{}situps!,simulatedexpensivecalculation(intensity));做一定时间的仰卧起坐}else{大于等于25时,又分两种情况ifrandomnumber3{随机数为3时,休息,不调用耗时算法println!(Takeabreaktoday!Remembertostayhydrated!);}else{否则调用耗时算法,生成跑步计划println!(Today,runfor{}minutes!,simulatedexpensivecalculation(intensity));}}}
根据以上代码的逻辑,耗时方法最多执行2次。不符合我们的目标。我们可以做以下修改:srcmain。rsfngenerateworkout(intensity:u32,randomnumber:u32){letexpensiveresultsimulatedexpensivecalculation(intensity);ifintensity25{println!(Today,do{}pushups!,expensiveresult);println!(Next,do{}situps!,expensiveresult);}else{ifrandomnumber3{println!(Takeabreaktoday!Remembertostayhydrated!);}else{println!(Today,runfor{}minutes!,expensiveresult);}}}
此时无论哪种情况,耗时代码只会运行一次。但是当intensity25并且randomnumber3时,并没有用到耗时方法的返回值,所以在此情况下会造成一次不必要的执行。这里的最佳方案是:只有在需要耗时方法的返回值时,才会调用它。可以使用闭包来实现:srcmain。rsfngenerateworkout(intensity:u32,randomnumber:u32){只是定义闭包(匿名函数),而不执行,只有它后跟着()时才会执行letexpensiveclosurenum{println!(calculatingslowly。。。);thread::sleep(Duration::fromsecs(2));num};ifintensity25{使用expensiveclosure(intensity)闭包println!(Today,do{}pushups!,expensiveclosure(intensity));println!(Next,do{}situps!,expensiveclosure(intensity));}else{ifrandomnumber3{println!(Takeabreaktoday!Remembertostayhydrated!);}else{println!(Today,runfor{}minutes!,expensiveclosure(intensity));}}}
但是这里当intensity25时,闭包依然被调用了两次。可以将expensiveclosure(intensity)的值绑定到一个变量上存起来,然后使用。但是我们不这样解决,后面再看具体实现。闭包的类型推断
闭包不强制要求标注参数和返回值类型,这一点与函数不同。因为函数是暴露给用户的接口,我们严格定义函数的参数和返回值类型,有利于用户的理解和使用。但是闭包并不用于暴露给用户,不用命名他们或暴露给库的用户调用。闭包通常很短小,通常只是在狭小的上下文中工作,编译器能推断出其类型。当然也可以手动标注类型,例:letexpensiveclosurenum:u32u32{println!(calculatingslowly。。。);thread::sleep(Duration::fromsecs(2));num};函数和闭包的定义语法比较
函数:fnaddonev1(x:u32)u32{x1}闭包:letaddonev2x:u32u32{x1}闭包:letaddonev3x{x1}闭包:letaddonev4xx1匿名函数体只有一句时,可省略{}闭包的类型推断
注意:闭包的定义最终只会为参数返回值推断出唯一具体的类型,例:fnmain(){letexampleclosurexx;letsexampleclosure(1);letnexampleclosure(1。1);}
此时就会报错,因为第一次使用时,编译器已经推断出参数与返回值是i32类型,已经确定了,后面再传浮点数时就会报错。