Rust学习笔记(四十)闭包实例(简单缓存)
针对三十九节中Cacher实现的限制,我们可以用HashMap代替结构体中的单个value。HashMap的key就是我们传入闭包的参数,value就是闭包执行的结果。当我们调用value方法时,首先看HashMap中有无与其对应的缓存,有则直接返回,无需执行闭包;没有则执行闭包,将传入的参数和返回的结果以键值对插入HashMap,最后返回结果。srclib。rspubmodcache{usestd::collections::HashMap;usestd::hash::Hash;F是闭包的类型,T是闭包的参数类型,R是闭包的返回类型pubstructCacherF,T,RwhereT:EqHashClone,由于T还是HashMap的key,所以它要求T实现了EqHashClone三个traitF:Fn(T)R,闭包的参数是T的引用,因为T传入闭包后,还需要将它和闭包的结果插入HashMap,如果传入的不是引用,那它的所有权移动至闭包内,导致后面无法插入HashMap{values:HashMapT,R,缓存闭包参数和闭包结果的键值对valuesetter:F,闭包}implF,T,RCacherF,T,RwhereT:EqHashClone,F:Fn(T)R,{pubfnnew(valuesetter:F)CacherF,T,R{Cacher{values:HashMap::new(),valuesetter:valuesetter,}}pubfnvalue(mutself,arg:T)R{HashMap中有想要的值,直接取出并返回,不需要调用闭包ifself。values。containskey(arg){self。values。get(arg)。expect(msg)}else{HashMap中没有想要的值执行闭包,计算出想要的值letvalue(self。valuesetter)(arg);由于后面要把参数和结果插入HashMap,所以参数和结果所有权被移动,无法直接返回只能重新从HashMap中通过参数的引用取出来再返回但是如果参数插入HashMap,那么它的所有权被移动,就无法使用它get值了所以这里先clone一个参数,然后把它当作key插入HashMap,这样后面依然能使用参数get对应的结果letkeyarg。clone();self。values。insert(key,value);返回该参数对应的结果self。values。get(arg)。expect(msg)}}}}srcmain。rsusestd::{thread,time::Duration};useadder::cache;项目(package)名::模块名fnmain(){letmutcachecache::Cacher::new(a:Stringi32{println!(参数是{}的闭包执行了。,a);letb:i32a。parse()。expect(msg);b});println!(获取缓存的值:{},cache。value(String::from(1)));println!(获取缓存的值:{},cache。value(String::from(1)));println!(获取缓存的值:{},cache。value(String::from(1)));println!(获取缓存的值:{},cache。value(String::from(4)));println!(获取缓存的值:{},cache。value(String::from(2)));println!(获取缓存的值:{},cache。value(String::from(2)));println!(获取缓存的值:{},cache。value(String::from(5)));}
执行以上代码:参数是1的闭包执行了。获取缓存的值:1获取缓存的值:1获取缓存的值:1参数是4的闭包执行了。获取缓存的值:4参数是2的闭包执行了。获取缓存的值:2获取缓存的值:2参数是5的闭包执行了。获取缓存的值:5
可以看出我们获取了3次字符串1对应的值,但是参数是1的闭包只执行了1次。同理,4、2、5也是一样的。
注:由于教程没有给出此方案的代码,所以以上代码都是基于个人理解实现的,若有错误,欢迎大家批评指正。使用闭包捕获环境
闭包可以访问定义它的作用域内的变量,而普通函数则不能。例:fnmain(){letx4;letequaltoxzzx;lety4;assert!(equaltox(y));}
以上代码可以正常执行。但如果把闭包换为函数:fnmain(){letx4;fnequaltox(z:i32)bool{zx}lety4;assert!(equaltox(y));}
运行会报错:cantcapturedynamicenvironmentinafnitem但是捕获环境会产生内存开销。闭包从所在环境捕获值的方式
与函数获得参数的三种方式一样:取得所有权:FnOnce。将环境中值的所有权移动进闭包,其名称的Once部分代表了闭包不能多次获取相同变量的所有权的事实,所以它只能被调用一次。可变借用:FnMut。不可变借用:Fn
创建闭包时,通过闭包对环境值的使用,Rust推断出具体使用哪个trait:所有的闭包都实现了FnOnce,因为所有闭包都至少会被调用一次没有移动捕获变量的实现了FnMut无需可变访问捕获变量的闭包实现了Fnmove关键字
在参数列表前使用move关键字,可以强制闭包取得它所使用的环境值的所有权,适用于:当我们需要将闭包传递给新线程以移动数据使其归新线程所有时,此技术最管用。例:fnmain(){letxvec!〔1,2,3〕;letequaltoxmovezzx;println!({:?},x);equaltox(vec!〔1,2,3〕);}
所有权被移动,后面在println中再次使用会报错。最佳实践
当指定Fntraitbound时之一时,首先用Fn,基于闭包体里的情况,如果需要FnOnce或FnMut时,编译器会给出提示。