在rust中,匿名函数(或者说闭包)大量存在,所以有必要再次讨论匿名函数的一些问题。
其中比较关键的是和FnXXX特质的关系,以及和被捕获变量的关系。
本文的目的在于确认几个要点:
一、FnOnce,FnMut,Fn简单比较
比较汇总表
分类 | 执行次数 | 是否可以修改捕获的外部变量 | 是否归还捕获的外部变量 | 备注 | FnOnce | 一次 | 可以 | 通常归还,但如果有move,则不会 | 适用于只执行一次的情况 | FnMut | 可以多次 | 可以 | 通常归还,但如果有move,则不会
| 适用于需要修改捕获外部变量情况 | Fn | 可以多次 | 不可以 | 通常归还,但如果有move,则不会 | 适用于不修改,且多次调用的情况 | 注意:
1.关于所有权是否归还的问题只是涉及到被捕获的变量,而非通过参数传递的变量。
2.其次,捕获的变量会不会被归还,还和是否使用move关键字有关
3.如果函数内部修改捕获的外部变量,则必然不会实现Fn
4.一个匿名函数是可以同时实现多个Fnxxx特质的(而且是自动实现的)
如果一个外部变量在匿名函数中被修改,那么匿名函数是否使用move都无关紧要,因为就是不写move,编译器也会补充上(move)。
换言之,move和FnMut不是必然相关。
move可以用于FnOnce,FnMut,Fn中,要不要用,关键看需要,而不是看匿名函数的类型:FnOnce,FnMut,Fn
二、匿名函数变量捕获要点
a、什么是捕获
一个变量不是定义在匿名函数内部,而是在匿名函数主调区域,但是在匿名函数中有使用,那么就认为该变量被匿名函数捕获,例如:- let name:String=String::from("21世纪的ai战争");
- let fx=||{println!("{}",name);};
- fx();
复制代码 在这个例子中,name被fx捕获了!
b、是否归还所有权
没有move就会归还!
c、肉眼判断实现了什么,或者说我怎么知道一个匿名函数倒是实现了三个特质的哪一个?
那么如何肉眼判断一个匿名函数到底实现了三个特质的哪一个? 再不需要写额外的代码的情况下.
如前只有如下结论:
1.如果函数内部有修改外部变量,则必然实现了Fn,FnMut,但不会实现Fn
2.关键字move不影响实现的具体特质类型
3.如果捕获变量,但是不修改,无论是否有使用move关键字,则都实现了FnOnce,FnMut,Fn
d.使用代码来判断一个匿名函数到底实现了什么特质- #[allow(non_snake_case)]
- fn test_FnOnce<T: FnOnce()>(f: T) {
- println!("调用FnOnce,只能一次");
- f();
- }
- #[allow(non_snake_case)]
- fn test_FnMut<T: FnMut()>(mut f: T) {
- println!("调用FnMut,多次执行");
- f();
- f();
- }
- #[allow(non_snake_case)]
- fn test_Fn<T: Fn()>(f: T) {
- println!("调用Fn,多次执行");
- f();
- f();
- }
- /**
- * 通过调用外部方法验证某个匿名函数到底实现了什么Fn特质
- */
- fn main() {
- let name:String="rust".to_string();
- let mut name_mut:String="rust".to_string();
- //1.0 一个匿名函数,如果捕获外部变量,但是并不对变量做修改,则实现了FnOnce, FnMut和Fn特质
- let fx1_no_move_no_mut=||println!("{}",name);
- test_FnOnce(fx1_no_move_no_mut);
- test_FnMut(fx1_no_move_no_mut);
- test_Fn(fx1_no_move_no_mut);
- println!("{}",name);
- // 2~3的测试表明
- // a.如果内部修改了变量,但是不使用move,那么实现了FnOnce,FnMut特质(但不会实现Fn特质).变量归还
- // b.如果内部没有修改变量,但是使用了move,那么实现了FnOnce, FnMut和Fn特质,变量不归还
- //2.0 一个匿名函数,捕获外部变量,但是对变量做了修改,则实现了FnOnce, FnMut特质(但不会实现Fn特质)
- let mut fx2_no_move_mut=||{
- name_mut.push_str("!fx2_no_move_mut");
- println!("{}",name_mut);
- };
- //test_FnOnce(fx2_no_move_mut);
- test_FnMut(fx2_no_move_mut);
- println!("归还后:{}",name_mut);
- //3.0 一个匿名函数,如果捕获外部变量,但是并不对变量做修改,则实现了FnOnce, FnMut和Fn特质
- //但因为使用move,所以是不归还
- let fx3_move_no_mut= move ||println!("{}",name);
- //test_FnOnce(fx3_move_no_mut);
- //test_FnMut(fx3_move_no_mut);
- test_Fn(fx3_move_no_mut);
- //println!("{}",name);
- //4.0 实现FnOnce,FnMut.不归还
- let fx4_move_mut= move ||{
- name_mut.push_str("!fx4_move_mut");
- println!("{}",name_mut);
- };
- //test_FnOnce(fx4_move_mut);
- //test_FnMut(fx4_move_mut);
- //println!("第二次归还后:{}",name_mut); //已经move了不会归还
- }
复制代码
在测试代码中,通过通用参数+特质限定的方式测试一个匿名函数自动实现的Fnxxx特质。
三、如何利用FnXXX特质
主要通过特质绑定的方式进行利用,例如:- let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
- fn test_Fn<T: Fn()>(f: T) {
- println!("调用Fn,多次执行");
- f();
- f();
- }
复制代码
当用上特质绑定的时候,常常需要关联到dyn(动态分发).
动态分发不是必须,关键看实际传递什么。
在rust中,Fnxxx特质大量用于限定函数/方法的参数
四、综合示例
- #[derive(Debug)]
- struct Book {
- title: String,
- author: String,
- age: u32,
- }
- impl Book {
- fn new(title: &str, author: &str, age: u32) -> Self {
- Book { title: title.to_string(), author: author.to_string(), age: age }
- }
- fn print(&self) {
- println!("{} 作者 {}(发行时年龄{})", self.title, self.author, self.age);
- }
- }
- #[allow(non_snake_case)]
- fn test_FnOnce<T: FnOnce()>(f: T) {
- println!("调用FnOnce,只能一次");
- f();
- }
- #[allow(non_snake_case)]
- fn test_FnMut<T: FnMut()>(mut f: T) {
- println!("调用FnMut,多次执行");
- f();
- f();
- }
- #[allow(non_snake_case)]
- fn test_Fn<T: Fn()>(f: T) {
- println!("调用Fn,多次执行");
- f();
- f();
- }
- fn main() {
- fn_test();
- fn_check();
- }
- /**
- * 主要通过特质绑定的方式限定匿名函数的特质,从而限定匿名函数的行为。
- */
- fn fn_test() {
- //1.0 测试FnOnce特质
- let book1 = Book::new("唐诗三百首", "孙洙(蘅塘退士)", 54);
- let f1 = || {
- book1.print();
- };
- test_FnOnce(f1);
- //这个ts.print还可以继续使用,说明它被FnOnce归还了。
- book1.print();
- //2.0 测试FnMut特质
- println!("-----------------------------------------");
- let mut book2 = Book::new("Rust程序设计语言", "Steve Klabnik, Carol Nichols", 45);
- println!("book2地址: {:p}", &book2);
- let mut f2 = move || {
- book2.age += 1;
- book2.print();
- //这里可以明显看出变量地址发生了变化,因为所有权转移了
- println!("book2地址: {:p}", &book2);
- };
- test_FnMut(f2);
- //println!("{}",book2.age); //book1不可用是因为move转移了所有权,且FnMut需要可变借用
- println!("-----------------------------------------");
- let book3 = Book::new("认识儿童绘画的特定作用", "卢ml", 13);
- println!("book3地址: {:p}", &book3);
- let f3 = || {
- println!("闭包内book3地址: {:p}", &book3);
- book3.print();
- };
- test_Fn(f3);
- println!("{}", book3.age); //book2仍然可用,因为Fn只捕获了不可变引用
- println!("外部book3地址: {:p}", &book3); //验证地址是否相同
- }
- /**
- * 通过绑定的方式改变匿名函数所实现的特质
- * 检测move关键字的作用:用还是不用其实不重要,主要靠编译器推断
- */
- fn fn_check() {
- println!("------------------------------------------------------");
- println!("靠肉眼识别实现了哪一种Fn?");
- println!("------------------------------------------------------");
- let mut name: String = String::from("21世纪的ai战争");
- //这里fx无论是否move都无所谓,因为FnMut必然会自动move
- println!("只要匿名函数内有修改外部变量,必然实现了FnMut,也必然实现了FnOnce特质。");
- let fx = || {
- name.push_str(",人类将如何应对?");
- println!("{}", name);
- };
- let mut boxFx: Box<dyn FnMut()> = Box::new(fx);
- boxFx();
- //println!("{}",name); // 已经move了,所以这里会报错
- //一个匿名函数是实现了Fn还是FnOnce,纯纯地看如何定义的。
- let name2: String = String::from("认识世界,认识自己从来都不是一件简单当然事情");
- println!("只要匿名函数内没有修改外部变量,必然实现了Fn特质。");
- let fx2 = || {
- println!("{}", name2);
- };
- let boxFx2: Box<dyn Fn()> = Box::new(fx2);
- boxFx2();
- boxFx2();
- println!("虽然fx3和fx2是一摸一样的,但是被boxFx3约束为FnOnce特质,所以不能再调用第二次。");
- let name3: String = String::from("嫁女与征夫,不如弃路旁");
- let fx3 = || {
- println!("{}", name3);
- };
- let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
- boxFx3();
- //boxFx3(); //再调用一次会报错,因为强制使用FnOnce约束
- }
复制代码
fn_test中还通过变量的地址来验证所有权是否改变- println!("book3地址: {:p}", &book3);
复制代码 这里使用宏println的:p格式
附:测试输出
关联文章
rust学习十三.1、RUST匿名函数(闭包)
rust学习二十.13、RUST的函数指针fn和匿名函数 |