rust进阶-基础.1.匿名函数和FnXXX特质

打印 上一主题 下一主题

主题 1828|帖子 1828|积分 5484

在rust中,匿名函数(或者说闭包)大量存在,所以有必要再次讨论匿名函数的一些问题。
其中比较关键的是和FnXXX特质的关系,以及和被捕获变量的关系。
本文的目的在于确认几个要点:
 
一、FnOnce,FnMut,Fn简单比较


  •  FnOnce-只能被调用一次.所捕获的变量全部权归还给主调代码
  •  FnMut-可以被多次调用,但是捕获的变量必须是可变的,全部权不归还
  •  Fn-可以被多次调用,全部权归还(不要求捕获的变量必须是可变的)
注意:关于全部权是否归还的问题只是涉及到被捕获的变量,而非通过参数传递的变量。
如果一个外部变量在匿名函数中被修改,那么匿名函数是否使用move都无关紧要,因为就是不写move,编译器也会增补上。
换言之,move和FnMut不是必然相干,move可以用于FnOnce,FnMut,Fn中,要不要用,关键看需要,而不是看匿名函数的类型:FnOnce,FnMut,Fn
参数的全部权变化是另外一个议题。
 
 
二、匿名函数变量捕获要点

a、什么是捕获
一个变量不是定义在匿名函数内部,而是在匿名函数主调区域,但是在匿名函数中有使用,那么就认为该变量被匿名函数捕获,例如:
  1. let name:String=String::from("21世纪的ai战争");
  2. let fx=||{println!("{}",name);};
  3. fx();
复制代码
在这个例子中,name被fx捕获了!
b、是否归还全部权?
要看匿名函数实现了什么特质(FnOnce,FnMut,Fn三个之一)
FnOnce:归还
FnMut:不归还
Fn:归还
但这也仅仅是简单情况下,实际还得联合move关键字,例如下例中,都是FnOnce,但是一个不还,一个还了:
  1. /**
  2. * 这个例子证明move和实现特质的类型没有关系
  3. */
  4. fn test_move(){
  5.     let name3: String = String::from("嫁女与征夫,不如弃路旁");
  6.     let fx3 = move || {
  7.         println!("{}", name3);
  8.     };
  9.     let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
  10.     boxFx3();
  11.     println!("{}", name3);  //这里不会报错,因为所有权已经被归还了。
  12. }
  13. fn test_no_move(){
  14.     let name3: String = String::from("嫁女与征夫,不如弃路旁");
  15.     let fx3 =  || {
  16.         println!("{}", name3);
  17.     };
  18.     let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
  19.     boxFx3();
  20.     println!("{}", name3);  
  21. }
复制代码
 
test_move()编译报错,因为move关键字导致name3全部权转移,而test_no_move不会!
 
c、肉眼判断实现了什么,或者说我怎么知道一个匿名函数倒是实现了三个特质的哪一个?
那么如何肉眼判断一个匿名函数到底实现了三个特质的哪一个?  再不需要写额外的代码的情况下.
如前只有如下结论:
1.如果使用了move,则必然实现了FnMut,也必然实现了FnOnce
2.如果匿名函数中有修改捕获变量,则必然实现了FnMut和FnOnce
3.如果匿名函数不修改捕获的变量,那么无从判断是实现了FnOnce还是Fn,但至少实现了FnOnce
4.可以通过特质绑定的方式,指定一个匿名函数所实现的特质之一:FnOnce,FnMut,Fn
5.如果没有特别定义,一般情况下一个匿名函数都是实现Fn,也就是说你可以调用这个匿名函数多次
 
三、如何使用FnXXX特质

主要通过特质绑定的方式举行使用,例如:
  1. let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
  2. fn test_Fn<T: Fn()>(f: T) {
  3.     println!("调用Fn,多次执行");
  4.     f();
  5.     f();
  6. }
复制代码
 
当用上特质绑定的时候,常常需要关联到dyn(动态分发).
 
四、综合示例
  1. #[derive(Debug)]
  2. struct Book {
  3.     title: String,
  4.     author: String,
  5.     age: u32,
  6. }
  7. impl Book {
  8.     fn new(title: &str, author: &str, age: u32) -> Self {
  9.         Book { title: title.to_string(), author: author.to_string(), age: age }
  10.     }
  11.     fn print(&self) {
  12.         println!("{} 作者 {}(发行时年龄{})", self.title, self.author, self.age);
  13.     }
  14. }
  15. #[allow(non_snake_case)]
  16. fn test_FnOnce<T: FnOnce()>(f: T) {
  17.     println!("调用FnOnce,只能一次");
  18.     f();
  19. }
  20. #[allow(non_snake_case)]
  21. fn test_FnMut<T: FnMut()>(mut f: T) {
  22.     println!("调用FnMut,多次执行");
  23.     f();
  24.     f();
  25. }
  26. #[allow(non_snake_case)]
  27. fn test_Fn<T: Fn()>(f: T) {
  28.     println!("调用Fn,多次执行");
  29.     f();
  30.     f();
  31. }
  32. fn main() {
  33.     fn_test();
  34.     fn_check();
  35. }
  36. /**
  37. * 主要通过特质绑定的方式限定匿名函数的特质,从而限定匿名函数的行为。
  38. */
  39. fn fn_test() {
  40.     //1.0 测试FnOnce特质
  41.     let book1 = Book::new("唐诗三百首", "孙洙(蘅塘退士)", 54);
  42.     let f1 = || {
  43.         book1.print();
  44.     };
  45.     test_FnOnce(f1);
  46.     //这个ts.print还可以继续使用,说明它被FnOnce归还了。
  47.     book1.print();
  48.     //2.0 测试FnMut特质
  49.     println!("-----------------------------------------");
  50.     let mut book2 = Book::new("Rust程序设计语言", "Steve Klabnik, Carol Nichols", 45);
  51.     println!("book2地址: {:p}", &book2);
  52.     let mut f2 = move || {
  53.         book2.age += 1;
  54.         book2.print();
  55.         //这里可以明显看出变量地址发生了变化,因为所有权转移了
  56.         println!("book2地址: {:p}", &book2);
  57.     };
  58.     test_FnMut(f2);
  59.     //println!("{}",book2.age);  //book1不可用是因为move转移了所有权,且FnMut需要可变借用
  60.     println!("-----------------------------------------");
  61.     let book3 = Book::new("认识儿童绘画的特定作用", "卢ml", 13);
  62.     println!("book3地址: {:p}", &book3);
  63.     let f3 = || {
  64.         println!("闭包内book3地址: {:p}", &book3);
  65.         book3.print();
  66.     };
  67.     test_Fn(f3);
  68.     println!("{}", book3.age); //book2仍然可用,因为Fn只捕获了不可变引用
  69.     println!("外部book3地址: {:p}", &book3); //验证地址是否相同
  70. }
  71. /**
  72. * 通过绑定的方式改变匿名函数所实现的特质
  73. * 检测move关键字的作用:用还是不用其实不重要,主要靠编译器推断
  74. */
  75. fn fn_check() {
  76.     println!("------------------------------------------------------");
  77.     println!("靠肉眼识别实现了哪一种Fn?");
  78.     println!("------------------------------------------------------");
  79.     let mut name: String = String::from("21世纪的ai战争");
  80.     //这里fx无论是否move都无所谓,因为FnMut必然会自动move
  81.     println!("只要匿名函数内有修改外部变量,必然实现了FnMut,也必然实现了FnOnce特质。");
  82.     let fx = || {
  83.         name.push_str(",人类将如何应对?");
  84.         println!("{}", name);
  85.     };
  86.     let mut boxFx: Box<dyn FnMut()> = Box::new(fx);
  87.     boxFx();
  88.     //println!("{}",name);  // 已经move了,所以这里会报错
  89.     //一个匿名函数是实现了Fn还是FnOnce,纯纯地看如何定义的。
  90.     let name2: String = String::from("认识世界,认识自己从来都不是一件简单当然事情");
  91.     println!("只要匿名函数内没有修改外部变量,必然实现了Fn特质。");
  92.     let fx2 = || {
  93.         println!("{}", name2);
  94.     };
  95.     let boxFx2: Box<dyn Fn()> = Box::new(fx2);
  96.     boxFx2();
  97.     boxFx2();
  98.     println!("虽然fx3和fx2是一摸一样的,但是被boxFx3约束为FnOnce特质,所以不能再调用第二次。");
  99.     let name3: String = String::from("嫁女与征夫,不如弃路旁");
  100.     let fx3 = || {
  101.         println!("{}", name3);
  102.     };
  103.     let boxFx3: Box<dyn FnOnce()> = Box::new(fx3);
  104.     boxFx3();
  105.     //boxFx3();  //再调用一次会报错,因为强制使用FnOnce约束
  106. }
复制代码
 
fn_test中还通过变量的地址来验证全部权是否改变
  1. println!("book3地址: {:p}", &book3);
复制代码
这里使用宏println的:p格式
 
附:测试输出

 
 
关联文章
rust学习十三.1、RUST匿名函数(闭包)

rust学习二十.13、RUST的函数指针fn和匿名函数

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

郭卫东

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表