長樂未央 發表於 2025-5-11 11:24:00

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

<p>在rust中,匿名函数(或者说闭包)大量存在,所以有必要再次讨论匿名函数的一些问题。</p>
<p>其中比较关键的是和FnXXX特质的关系,以及和被捕获变量的关系。</p>
<p>本文的目的在于确认几个要点:</p>
<p>&nbsp;</p>
<h1><span style="font-size: 24px"><strong>一、FnOnce,FnMut,Fn简单比较</strong></span></h1>
<p><strong>比较汇总表</strong></p>
<table style="border-collapse: collapse; width: 43.5381%; height: 105px" border="1">
<tbody>
<tr style="height: 42px; background-color: rgba(206, 212, 217, 1)">
<td style="width: 14.2509%; height: 42px">分类</td>
<td style="width: 14.4945%; height: 42px">执行次数</td>
<td style="width: 14.1322%; height: 42px">是否可以修改捕获的外部变量</td>
<td style="width: 23.5048%; height: 42px">是否归还捕获的外部变量</td>
<td style="width: 33.7393%; height: 42px">备注</td>
</tr>
<tr style="height: 21px">
<td style="width: 14.2509%; height: 21px">FnOnce</td>
<td style="width: 14.4945%; height: 21px">一次</td>
<td style="width: 14.1322%; height: 21px">可以</td>
<td style="width: 23.5048%; height: 21px">通常归还,但如果有move,则不会</td>
<td style="width: 33.7393%; height: 21px">适用于只执行一次的情况</td>
</tr>
<tr style="height: 21px">
<td style="width: 14.2509%; height: 21px">FnMut</td>
<td style="width: 14.4945%; height: 21px">可以多次</td>
<td style="width: 14.1322%; height: 21px">可以</td>
<td style="width: 23.5048%; height: 21px">
<p>通常归还,但如果有move,则不会</p>
</td>
<td style="width: 33.7393%; height: 21px">适用于需要修改捕获外部变量情况</td>
</tr>
<tr style="height: 21px">
<td style="width: 14.2509%; height: 21px">Fn</td>
<td style="width: 14.4945%; height: 21px">可以多次</td>
<td style="width: 14.1322%; height: 21px">不可以</td>
<td style="width: 23.5048%; height: 21px">通常归还,但如果有move,则不会</td>
<td style="width: 33.7393%; height: 21px">适用于不修改,且多次调用的情况</td>
</tr>
</tbody>
</table>
<p>注意:</p>
<p>1.关于所有权是否归还的问题只是涉及到被捕获的变量,而非通过参数传递的变量。</p>
<p>2.其次,捕获的变量会不会被归还,还和是否使用move关键字有关</p>
<p>3.如果函数内部修改捕获的外部变量,则必然不会实现Fn</p>
<p>4.一个匿名函数是可以同时实现多个Fnxxx特质的(而且是自动实现的)</p>
<p>&nbsp;</p>
<p>如果一个外部变量在匿名函数中被修改,那么匿名函数是否使用move都无关紧要,因为就是不写move,编译器也会补充上(move)。</p>
<p>换言之,move和FnMut不是必然相关。</p>
<p>move可以用于FnOnce,FnMut,Fn中,要不要用,关键看需要,而不是看匿名函数的类型:FnOnce,FnMut,Fn</p>
<p>&nbsp;</p>
<h1><span style="font-size: 24px"><strong>二、匿名函数变量捕获要点</strong></span></h1>
<p><strong>a、什么是捕获</strong></p>
<p>一个变量不是定义在匿名函数内部,而是在匿名函数主调区域,但是在匿名函数中有使用,那么就认为该变量被匿名函数捕获,例如:</p>
<pre class="highlighter-prismjs prismjs-lines-highlighted language-rust" tabindex="0"><code>let name:String=String::from("21世纪的ai战争");
let fx=||{println!("{}",name);};
fx();</code></pre>
<p>在这个例子中,name被fx捕获了!</p>
<p><strong>b、是否归还所有权</strong></p>
<p>没有move就会归还!</p>
<p><strong>c、肉眼判断实现了什么,或者说我怎么知道一个匿名函数倒是实现了三个特质的哪一个?</strong></p>
<p>那么如何肉眼判断一个匿名函数到底实现了三个特质的哪一个?&nbsp; 再不需要写额外的代码的情况下.</p>
<p>如前只有如下结论:</p>
<p>1.如果函数内部有修改外部变量,则必然实现了Fn,FnMut,但不会实现Fn</p>
<p>2.关键字move不影响实现的具体特质类型</p>
<p>3.如果捕获变量,但是不修改,无论是否有使用move关键字,则都实现了FnOnce,FnMut,Fn</p>
<p>&nbsp;</p>
<p><strong>d.使用代码来判断一个匿名函数到底实现了什么特质</strong></p>
<pre class="highlighter-prismjs prismjs-lines-highlighted language-java" tabindex="0"><code>#
fn test_FnOnce&lt;T: FnOnce()&gt;(f: T) {
    println!("调用FnOnce,只能一次");
    f();
}

#
fn test_FnMut&lt;T: FnMut()&gt;(mut f: T) {
    println!("调用FnMut,多次执行");
    f();
    f();
}
#
fn test_Fn&lt;T: Fn()&gt;(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了不会归还
}</code></pre>
<p>&nbsp;</p>
<p>在测试代码中,通过通用参数+特质限定的方式测试一个匿名函数自动实现的Fnxxx特质。</p>
<h1><span style="font-size: 24px"><strong>三、如何利用FnXXX特质</strong></span></h1>
<p><strong><span style="font-size: 15px">主要通过特质绑定的方式进行利用,例如:</span></strong></p>
<pre class="highlighter-prismjs prismjs-lines-highlighted language-rust" tabindex="0"><code>let boxFx3: Box&lt;dyn FnOnce()&gt; = Box::new(fx3);

fn test_Fn&lt;T: Fn()&gt;(f: T) {
    println!("调用Fn,多次执行");
    f();
    f();
}</code></pre>
<p>&nbsp;</p>
<p>当用上特质绑定的时候,常常需要关联到dyn(动态分发).</p>
<p>动态分发不是必须,关键看实际传递什么。</p>
<p>&nbsp;</p>
<p>在rust中,Fnxxx特质大量用于限定函数/方法的参数</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h1>四、综合示例</h1>
<pre class="highlighter-prismjs prismjs-lines-highlighted language-rust" tabindex="0"><code>#
struct Book {
    title: String,
    author: String,
    age: u32,
}

impl Book {
    fn new(title: &amp;str, author: &amp;str, age: u32) -&gt; Self {
      Book { title: title.to_string(), author: author.to_string(), age: age }
    }

    fn print(&amp;self) {
      println!("{} 作者 {}(发行时年龄{})", self.title, self.author, self.age);
    }
}

#
fn test_FnOnce&lt;T: FnOnce()&gt;(f: T) {
    println!("调用FnOnce,只能一次");
    f();
}

#
fn test_FnMut&lt;T: FnMut()&gt;(mut f: T) {
    println!("调用FnMut,多次执行");
    f();
    f();
}
#
fn test_Fn&lt;T: Fn()&gt;(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}", &amp;book2);
    let mut f2 = move || {
      book2.age += 1;
      book2.print();
      //这里可以明显看出变量地址发生了变化,因为所有权转移了
      println!("book2地址: {:p}", &amp;book2);
    };
    test_FnMut(f2);
    //println!("{}",book2.age);//book1不可用是因为move转移了所有权,且FnMut需要可变借用

    println!("-----------------------------------------");
    let book3 = Book::new("认识儿童绘画的特定作用", "卢ml", 13);
    println!("book3地址: {:p}", &amp;book3);
    let f3 = || {
      println!("闭包内book3地址: {:p}", &amp;book3);
      book3.print();
    };
    test_Fn(f3);
    println!("{}", book3.age); //book2仍然可用,因为Fn只捕获了不可变引用
    println!("外部book3地址: {:p}", &amp;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&lt;dyn FnMut()&gt; = Box::new(fx);
    boxFx();
    //println!("{}",name);// 已经move了,所以这里会报错

    //一个匿名函数是实现了Fn还是FnOnce,纯纯地看如何定义的。
    let name2: String = String::from("认识世界,认识自己从来都不是一件简单当然事情");
    println!("只要匿名函数内没有修改外部变量,必然实现了Fn特质。");
    let fx2 = || {
      println!("{}", name2);
    };
    let boxFx2: Box&lt;dyn Fn()&gt; = Box::new(fx2);
    boxFx2();
    boxFx2();

    println!("虽然fx3和fx2是一摸一样的,但是被boxFx3约束为FnOnce特质,所以不能再调用第二次。");
    let name3: String = String::from("嫁女与征夫,不如弃路旁");
    let fx3 = || {
      println!("{}", name3);
    };
    let boxFx3: Box&lt;dyn FnOnce()&gt; = Box::new(fx3);
    boxFx3();
    //boxFx3();//再调用一次会报错,因为强制使用FnOnce约束
}</code></pre>
<p>&nbsp;</p>
<p>fn_test中还通过变量的地址来验证所有权是否改变</p>
<pre class="highlighter-prismjs prismjs-lines-highlighted language-rust" tabindex="0"><code>println!("book3地址: {:p}", &amp;book3);</code></pre>
<p>这里使用宏println的:p格式</p>
<p>&nbsp;</p>
<p>附:测试输出</p>
<p><img src="https://img2024.cnblogs.com/blog/1177268/202505/1177268-20250511112300871-1373096185.png"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><strong>关联文章</strong></p>
<h1 class="postTitle" style="padding: 10px 10px 10px 0; font-size: 14px; font-weight: bold; color: rgba(0, 0, 0, 1); font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0; text-transform: none; widows: 2; word-spacing: 0; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial"><span style="vertical-align: middle" role="heading" aria-level="2">rust学习十三.1、RUST匿名函数(闭包)</span></h1>
<h1 class="postTitle" style="padding: 10px 10px 10px 0; font-size: 14px; font-weight: bold; color: rgba(0, 0, 0, 1); font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0; text-transform: none; widows: 2; word-spacing: 0; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial"><span style="vertical-align: middle" role="heading" aria-level="2">rust学习二十.13、RUST的函数指针fn和匿名函数</span></h1>
<div class="postBody" style="line-height: 1.8; margin: 0; color: rgba(0, 0, 0, 1); font-family: &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0; text-transform: none; widows: 2; word-spacing: 0; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial">
<div id="cnblogs_post_body" class="blogpost-body blogpost-body-html" style="margin: 0 0 20px; line-height: 1.8">
<div class="current-collection" style="max-width: 540px; background: var(--cnblogs-current-collection-bg-color); border-radius: 4px; margin: 0; line-height: 1.8">
<div class="current-collection-title" style="font-weight: bold; font-size: 1.2em; padding: 10px 15px 10px 10px; display: flex; align-items: center; justify-content: space-between; margin: 0; line-height: 1.8">&nbsp;</div>
</div>
</div>
</div>

</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:正在战斗中,转载请注明原文链接:https://www.cnblogs.com/lzfhope/p/18866757</p><br><br>
来源:https://www.cnblogs.com/lzfhope/p/18866757
頁: [1]
查看完整版本: rust进阶-基础.1.匿名函数和FnXXX特质