前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust学习笔记Day23 闭包的使用场景,3种常用闭包类型有哪些

Rust学习笔记Day23 闭包的使用场景,3种常用闭包类型有哪些

作者头像
用户1072003
发布2023-02-23 17:01:59
6300
发布2023-02-23 17:01:59
举报
文章被收录于专栏:码上读书

昨天我们一起学习了闭包的定义及影响闭包大小的因素

今天我们接着学习 FnOnce / FnMut / Fn 这三种闭包类型。

FnOnce

代码定义如下:

代码语言:javascript
复制
pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

Output: 是FnOnce的关联类型,是闭包的返回值类型。call_once: 第一个参数是self,它会转移self的所有权到call_once函数里。Args: 是泛型参数。

FnOnce 被称作 Once :它只能被调用一次。再次调用,编译器就会报变量已经被 move 这样的常见所有权错误了。

比如这个例子:

代码语言:javascript
复制
fn main() {
    let name = String::from("name");
    // 这个闭包啥也不干,只是把捕获的参数返回去
    let c = move |greeting: String| (greeting, name);

    let result = c("greeting".to_string());

    println!("result: {:?}", result);

    // 无法再次调用
    // let result = c("hi".to_string());
}

闭包c 只是把参数(greeting)和捕获的(name)返回了。这里会转移闭包内部数据,导致闭包不完整,无法再次使用,所以这里的c是一个FnOnce的闭包。最后一次调用会报错。

作者解释道: 因为把闭包内部数据转移(返回)了,所以只能调用一次,那如果我们不转移呢?

再试一次:

代码语言:javascript
复制
fn main() {
    let name = String::from("name");
    let c = move |greeting: String| (println!("{} {}",greeting, name));

    c("greeting".to_string());
    c("hello".to_string());
}

结果如下:

代码语言:javascript
复制
greeting name
hello name

可以看到这回就可以重复执行闭包c了,这时候闭包c就不是FnOnce。

FnMut

看到mut,其实我第一个想到的就是 可变变量:

代码语言:javascript
复制
let mut a = xxx;

我理解这个 FnMut 就是可变闭包,或者说是可以写操作的闭包。

还是先看一下他的代码定义:

代码语言:javascript
复制
pub trait FnMut<Args>: FnOnce<Args> {
    extern "rust-call" fn call_mut(
        &mut self, 
        args: Args
    ) -> Self::Output;
}

FnOnce 是 FnMut 的 super trait。

  1. FnMut也有FnOnce里的 Output 这个关联类型和call_once这个方法。
  2. 还有自己的一个call_mut方法。

可以看到 call_mut 的参数是 &mut self,它并不转移self,所以可以多次调用

如果想要在FnMut闭包内修改捕获的变量,外部变量也要mut 一下。

看个例:

代码语言:javascript
复制
fn main() {
    let mut name = String::from("name");
    let mut name1 = String::from("hello");

    // 捕获 &mut name ,name 需要声明成 mut
    let mut c = || {
        name.push_str(" 0");
        println!("c: {}", name);
    };

    // 捕获 mut name1,name1 也需要声明成 mut
    let mut c1 = move || {
        name1.push_str("1");
        println!("c1: {}", name1);
    };

    c();
    c1();

    call_mut(&mut c);
    call_mut(&mut c);
    call_mut(&mut c1);
    call_mut(&mut c1);

    call_once(c);
    call_once(c1);
}

// 在作为参数时,FnMut 也要显式地使用 mut,或者 &mut
fn call_mut(c: &mut impl FnMut()) {
    c();
}

// 想想看,为啥 call_once 不需要 mut?
fn call_once(c: impl FnOnce()) {
    c();
}

运行结果如下:

代码语言:javascript
复制
c: name 0
c1: hello1
c: name 0 0
c: name 0 0 0
c1: hello11
c1: hello111
c: name 0 0 0 0
c1: hello1111

这里在闭包c里捕获了&mut name,因为没有move所有权,所以是借用。在闭包c1里捕获了mut name1,因为move了name1的所有权。

然后演示了call_mut函数的多次调用, 需要使用 &mut self,所以不移动所有权。

Fn

再来看下Fn trait,定义如下:

代码语言:javascript
复制
pub trait Fn<Args>: FnMut<Args> {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

Fn“继承”了 FnMut,或者说 FnMut 是 Fn 的 super trait。

这样一来,** 用FnOnce或FnMut的时候,都可以用Fn的闭包来满足**。

注意:Fn和fn不是一回事儿。fn 是一个 function pointer,不是闭包

使用场景

  1. thread::spawn。
  2. Iterator trait里 大部分函数都接收一个闭包。如map。
  3. 为闭包实现某个trait,让它可以有其他的行为。

小结

Rust闭包效率非常高。

  1. 闭包里捕获的外部变量,都存储在栈上,没有堆内存的分配。
  2. 闭包在创建时,会隐式的创建自己的类型,每个闭包都是一个新的类型。
  3. 不需要额外的函数指针来运行闭包,效率几乎和函数一样。

然后介绍了3种闭包:FnOnce、FnMut 和 Fn。

  • FnOnce 只能调用一次;
  • FnMut 允许在执行时修改闭包的内部数据,可以执行多次;
  • Fn 不允许修改闭包的内部数据,也可以执行多次。

这里有点奇怪的是:FnMut是Fn的super trait,但是FnMut可以修改闭包内部数据,而Fn却不允许修改闭包内部数据?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-02-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码上读书 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • FnOnce
  • FnMut
  • Fn
  • 使用场景
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档