rust 闭包

Created

2024-05-14 09:00:30

Updated

2024-10-27 14:43:06

1 底层原理

Tip
  • 闭包是可以捕获其定义所在的作用域的匿名函数
  • 闭包底层实际上是一个匿名的结构体,且实现了Fn,FnMut,FnOnce这三个trait中的一个或多个
  • 闭包会根据对捕获的变量的使用情况,推断出实现了哪些 trait

我们先来看看下面的代码

我们需要切换到nightly版本
# 在 项目目录下执行, 只更换当前目录的rust版本
rustup override set nightly
运行即可, 不会报错的
#![feature(unboxed_closures)]
#![feature(fn_traits)]

struct Adder {
    a: u32
}

// Adder 只实现了FnOnce (后面我们还会提到什么叫只实现了)
impl FnOnce<(u32, )> for Adder {
    type Output = u32;
    extern "rust-call" fn call_once(self, b: (u32, )) -> Self::Output {
        // 相当于闭包的"函数体"
        self.a + b.0
    }
}

fn main() {
    let x=3;
    let adder = Adder { a: x }; // 相当于闭包将捕获的外部变量x存到结构体中的a
    // adder 结构体实现了上面的FnOnce trait, 这里变成可以用函数的方式()去调用自身了
    let res=adder(2);
    // 提示错误, adder 被move掉了, 原因看最后
    // adder(2);
    println!("{}",res);// 5

    let adder = Adder { a: 3 };
    // adder()是一种简写方式, 其实相当于调用如下
    let res=adder.call_once((2,));
    println!("{}",res);// 5

    let adder = Adder { a: 3 };
    // adder.call_once 也是语法糖,这个我们在结构体章节中已经说到,
    // 所以实际是这样调用 ,adder 作为参数传递给call_once 方法
    let res=FnOnce::call_once(adder,(2,));
    println!("{}",res);// 5
    // 提示adder已经被move了, 因为call_once 传递的是self
    // println!("{:p}",&adder);

}
Important

可以将2个trait方法里的 {self.a;} 代码注释,再执行看看

#![feature(unboxed_closures)]
#![feature(fn_traits)]

struct Adder<'a> {
    a: String,
    b: &'a str,
    c: &'a mut u32,
}
impl<'a> FnOnce<(u32,)> for Adder<'a> {
    type Output = (); // 看你闭包实际的返回情况, 这里进行修改
    extern "rust-call" fn call_once(self, b: (u32,)) -> Self::Output{
        println!("{}",self.a);
        println!("{}",self.b);
        *self.c=22;
        {
            self.a;
        }
    }
}
impl<'a> FnMut<(u32,)> for Adder<'a> {
    extern "rust-call" fn call_mut(&mut self, b: (u32,)) -> Self::Output{
        println!("{}",self.a);
        println!("{}",self.b);
        *self.c=22;
        {
            // 首先捕获 外部变量的所有权,模拟闭包捕获外部变量所有权
                // 捕获变量的方式不是重点, 重要的是闭包内部对该变量的使用方式,才影响闭包实现了哪个trait的原因
            // 如果闭包里想要将捕获的所有权变量 给弄失效,那么你就必须只能传递 self
            // 所以这里你就实现不了FnMut trait,因为它传递的是&mut,是无法将a失效化的
            // 这里当然报错了.
            self.a;
        }
    }
}

fn main() {
    let a = "abc".to_string();
    let b = "abc";
    let mut c = 11;
    let mut adder = Adder {
        a: a, // 模拟闭包捕获所有权
        b: b,  // 模拟闭包捕获不可变借用
        c: &mut c  // 模拟闭包捕获可变借用
    };
    // println!("{}",a);
    let res = adder(2);
    println!("{}",c);

}
Caution
  • 其实好理解,使用fn trait的方法是最好的, 因为FnOnce 那个没必要执行完就让结构体失效了, 也没必要用FnMut,它需要mut.
  • 看 一个闭包到底实现了 哪个trait,只要想象将闭包体放入3个trait的要实现的方法里看能否执行成功, 成功就表明实现了这个trait
#![feature(unboxed_closures)]
#![feature(fn_traits)]

struct Adder<'a> {
    a: String,
    b: &'a str,
    c: &'a mut u32,
}
impl<'a> FnOnce<(u32,)> for Adder<'a> {
    type Output = (); // 看你闭包实际的返回情况, 这里进行修改
    extern "rust-call" fn call_once(self, b: (u32,)) -> Self::Output{
        println!("{}",self.a);
        println!("{} call_once",self.b);
    }
}
impl<'a> FnMut<(u32,)> for Adder<'a> {
    extern "rust-call" fn call_mut(&mut self, b: (u32,)) -> Self::Output{
        println!("{}",self.a);
        println!("{} call_mut",self.b);
    }
}
impl<'a> Fn<(u32,)> for Adder<'a> {
    extern "rust-call" fn call(&self, b: (u32,)) -> Self::Output{
        println!("{}",self.a);
        println!("{} call",self.b);
    }
}
fn main() {
    let a = "abc".to_string();
    let b = "abc";
    let mut c = 11;
    // 这里其实不用mut, 因为最后实际是执行的 Fn trait
    let mut adder = Adder {
        a: a, // 捕获所有权
        b: b,  // 捕获不可变借用
        c: &mut c  // 捕获可变借用
    };
    // 实现全部trait的"闭包" ,执行看看是执行哪个trait的方法
    let res = adder(2);
    adder(2);
}

2 简单使用

fn main() {
    fn add_one_v1(x: u32) -> u32 {
        x + 1
    }
    // 闭包,匿名函数, |参数| -> 返回值 { 函数体}
    let add_one_v2 = |x: u32| -> u32 { x + 1 };
    // 等价与 |x| {x + 1}  因为只有一个表达式, 所以 {} 可以省略
    let add_one_v3 = |x| x + 1;
    // 上面的定义没有类型, 如果没有的调用语句, 是会报错的, 因为函数没有类型,内存占用无法确定
    // 调用后, 编译器推断出参数类型,这个时候类型就确定了
    let r = add_one_v3(1);
    println!("{}", r);
    // 后续如果你用其他类型,就会报错的
    // let r2 = add_one_v3("a");
}

3 捕获值的方式

3.1 捕获逻辑

Important
  • 在没有使用move 的情况下
    • 如果使用不可变借用就可以的, 那么就使用不可变借用(既然只要不可变就OK,何必搞其他的”权限”更多的捕获,很简单的道理)
    • 其次是需要修改的情况下,如果用可变借用就能完成逻辑,那就用可变借用
    • 最后没有办法,只能通过获取所有权来完成逻辑, 那么就只能获取所有权
  • move 关键字
    • 强制获取外部变量的所有权, 即使你本身可能不需要
    • 当然还有本身copy类型的变量, 不会让外部的变量失效

3.2 不可变借用

简单的明显的例子
fn main() {
    let list = vec![1, 2, 3];
    println!("Before defining closure: {:?}", list);
    // 捕获了闭包所在环境, 变量list
    let only_borrows = || {
        // println!使用了 list的不可变借用
        println!("From closure: {:?}", list);
    }

    println!("Before calling closure: {:?}", list);
    only_borrows();
    println!("After calling closure: {:?}", list);
}
看起来有点不那么明显的例子
fn main() {
    let mut x = 1;
    let f = |y: i32| x + y;
    x = 3;  //报错了
    let r = f(2);
    let mut x = 1;
    // 这里同样也是使用x的不可变借用
    let f = Box::new(|y: i32| x + y);
}
结果可以发现是不可变借用
  • 运行根据提示的错误信息, 闭包里捕获的x 是 不可变借用
  • 根据前面的判断逻辑, 该闭包只要不可变借用就能完成它的逻辑,所以使用不可变借用
fn main(){
    let a = 1;
    let b = 2;
    let c = &a + b;  // 这种是ok的.会自动解引用的
    println!("{c}");
}
fn t<'a>(x: i32) -> &'a i32 {
    &x
    // 执行完t(x) x就失效了, 而你返回了x的不可变借用,所以有问题
}
// 问题同上, 会报错
fn ret_closure(x: i32) -> Box<dyn Fn(i32) -> i32> {
    // 传来的x, 再调用完 ret_closure(x) 后, x 就失效了,
    // 而这里返回的 ,使用了x的不可变借用
    Box::new(|y: i32| x + y)
}
// 正确的写法
fn ret_closure2(x: i32) -> Box<dyn Fn(i32) -> i32> {
    // x的所有权转移 就OK了
    Box::new(move |y: i32| x + y)
}

3.3 可变借用

fn main() {
    let mut list = vec![1, 2, 3];
    println!("Before defining closure: {:?}", list);
    // 需要mut, 看闭包体里如何使用捕获的变量
    let mut borrows_mutably = || list.push(7);

    borrows_mutably();
    println!("After calling closure: {:?}", list); // [1, 2, 3, 7]
}

3.4 所有权

move 种类的类型
#[derive(Debug)]
struct Foo;
fn main() {
    let a = Foo;
    println!(" {:p}", &a);
    // 整体去看闭包体{} 里是如何使用捕获的变量的.
    // 一旦需要使用所有权这样的权限, 那么就 只能实现FnOnce 那个trait了.
    let f =  || {
        // a 最后被move掉了,那么就需要有它的所有权
        // 那么首先 就是获取a的所有权
        // 所以这里的a就已经是从外部转移所有权后的a, 那么就和外部的地址不一样
        println!(" {:p}", &a);
        // 最后转移了a的所有权了.
        let b = a;
    };
    f();
    // 报错,a已经在闭包内被b获取所有权了
    // println!("{:?}", a);
}
同上,例子2
fn main() {
    let mut farewell = "goodbye".to_owned();
    let diary = || {
        println!("I said {}.", farewell);
        {
            farewell;
        } // farewell 被drop掉了,
    };
    // println!("{}", farewell);
}
使用move强制获取所有权,即使里面使用的只是不可变借用
use std::thread;

fn main() {
    let list = vec![1, 2, 3];
    println!("Before defining closure: {:?}", list);
    // 指定move, 所有权移动
    thread::spawn(move || println!("From thread: {:?}", list))
        .join()
        .unwrap();
    // println!("after defining closure: {:?}", list); // 被move了,报错
}
copy 种类的类型 使用move
fn main() {
    let x = 7;
    println!("外部x变量地址: {:p}", &x);
    let f = move || {
        // 虽然内部是用的 x 的不可变借用, 但是使用了move
        // move 后, 这里的x 就已经是新的x ,而不是外部x的借用了
        println!("move后 闭包内x变量地址: {:p}", &x);
        println!("{}", x);
    };
    f();
    println!("{}", x); // copy 类型, 值还有效
}
Move 种类的类型 使用move,但是闭包内部没有使用到所有权的权限
fn main() {
    let x = "abc".to_string();
    println!("外部x变量地址: {:p}", &x);
    let f = move || {
        println!("move后 闭包内x变量地址: {:p}", &x);
        println!("{}", x);
    };
    f();
    // ok 说明其实 Fn trait也实现了
    // 也说明了前面我们提到的, 将3个traint 都实现一遍, 看哪个能实现来判断
    f();
    // println!("{}", x); // 报错了.
}

4 trait

4.1 FnOnce

Tip
  1. FnOnce表示可以被调用一次,很显然我们编写的闭包肯定是要能被调用至少一次的.只要能调用至少一次,那么它就实现了FnOnce trait
  2. 如果只实现了 FnOnce这个trait, 其他trait没有实现,那么表示该闭包只能被调用一次, 反之亦然.
  3. 当你闭包被调用一次后,里面的捕获的值如果就失效了,就能推断它 只实现了 FnOnce trait,因为它被调用一次后,里面变量值无效了,再调用就报错了]{.txt-highlight}
源码定义
pub trait FnOnce<Args: Tuple> {
    /// The returned type after the call operator is used.
    #[lang = "fn_once_output"]
    #[stable(feature = "fn_once_output", since = "1.12.0")]
    type Output;

    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
得出结论
  • 当我们调用了只实现了 FnOnce trait的闭包比如 my_closure() 后, 它相当于调用 my_closure.call_once(),前面我们说过 闭包底层实际是一个结构体, 那么这里🈶转换为 相当于执行FnOnce::call_once(my_closure,args),转移所有权了, 所以只能调用一次
  • 结合底层原理章节看看
  • 所以很好理解, 为什么说只实现了 FnOnce trait的闭包 ,就只能调用一次
  • 这里再次强调: 具体实现了哪些trait,看底层原理将三个trait都套进去看能否成功执行
有捕获可变借用,不可变借用和所有权, 最后是只实现了FnOnce trait的闭包
fn main() {
    let farewell = "goodbye".to_owned();
    let mut b = 11;
    let c = "abc".to_string();
    println!("inner {:p}.", &farewell);
    let diary = || {
        // farewell move给了diary(作为返回值),那么就需要有它的所有权
        // 这个时候的farewell 就已经是获取了外部farewell所有权的新farewell
        println!("inner farewell {:p}.", &farewell);
        println!("inner  {}.", &mut b);
        println!("inner  {}.", &c);
        farewell
    };
    diary();
    // diary(); //报错
    // println!("{}", farewell); //报错
}
使用move但是实际可以被调用多次
fn main() {
    let farewell = "goodbye".to_owned();
    let diary = move || {
        println!("I said {}.", farewell);
    };
    diary();
    diary();
}

4.2 FnMut

源码
pub trait FnMut<Args: Tuple>: FnOnce<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
得出结论
  • 我们可以看到,实现了FnMut trait的闭包,肯定也实现FnOnce trait
  • 只实现 FnMut和FnOnce2个trait 的闭包,调用时 相当于 FnMut::call_mut(&mut self,args)
Tip
  1. 只要闭包最终只使用了捕获的值的可变借用,不管它是否真的进行修改,都说明只实现了FnMut+FnOnce
fn apply<F>(mut f: F)
where
    F: FnMut(),
{
    f();
    f();
}
fn main() {
    let mut farewell = "goodbye".to_owned();
    let mut diary = || {
        // 可变借用 String::push_str(&mut self,string: &str)
        farewell.push_str("hello");
    };
    // 根据底层原理, 看 FnMut::call_mut(&mut self)
    // 所以diary本身需要 let mut diary
    diary();
    apply(diary);
}
fn main(){
    struct Diary<'a> {
       tmp: &'a mut String,
    }
    let mut s="goodbye".to_string();
    let mut diary=Diary{tmp:&mut s};
    impl<'a> FnOnce<(u32,)> for Diary<'a> {
        type Output = ();
        extern "rust-call" fn call_once(self, b: (u32,)) -> Self::Output{
            self.tmp.push_str("hello");
            println!("{} call_once",self.tmp);
        }
    }
    impl<'a> FnMut<(u32,)> for Diary<'a> {
        extern "rust-call" fn call_mut(&mut self, b: (u32,)) -> Self::Output{
            self.tmp.push_str("hello");
            println!("{} call_mut",self.tmp);
        }
    }
    // 实现不了该trait
    // impl<'a> Fn<(u32,)> for Diary<'a> {
    //     extern "rust-call" fn call(&self, b: (u32,)) -> Self::Output{
    //         /* 
    //         如果有些知识点不知道, 可能会觉得 这个好像也OK
    //         String::push_str(&mut Self,&str)
    //         因为self.tmp 是 &mut farewell,将它传递给String::push_str好像很符合
    //         而实际上 会将&mut (*self.tmp)作为参数传递, self 是不可变借用, 这里又使用了可变借用,所以报错
    //         */ 
    //         self.tmp.push_str("hello");
    //         println!("{} call_mut",self.tmp);
    //     }
    // }
    diary(1);
}

可变借用函数传参 reborrow

下面的代码关注一下高亮行即可
fn main() {
struct Adder<'a> {
        a: String,
        b: &'a str,
        c: &'a mut u32,
    }
    let a = "abc".to_string();
    let b = "abc";
    let mut d = 11;
    println!("{:p}", &d);
    let adder = Adder {
        a: a,
        b: b,
        c: &mut d,
    };
    // *adder.c 是 *(adder.c)
    let f = &(*adder.c);
    print_type_of(f); //表示*adder.c 是d 就是 u32 类型
    // 这是对 d的可变reborrow, 所以 后面的2个可变借用实际对d的修改操作 是ok的
    let f = &mut (*adder.c);
    print_type_of(&f); //表示&mut (*adder.c) 是 &mut u32 类型
    *f = 22; // ok
    *adder.c = 33;  // ok
    println!("{}", d);
    println!("=============");
    //========================
    let a = "abc".to_string();
    let b = "abc";
    let mut d = 11; 
    let adder = &Adder {
        a: a,
        b: b,
        c: &mut d,
    };
    let f=&(adder.c);
    //结果表示 adder.c是 &mut u32 类型
    print_type_of(f); //&mut u32
    let f=&(*adder.c);
    // 结果表示 *adder.c 是 u32类型
    print_type_of(f); // u32
    //报错, 无法borrow 一个 &引用
    // let f = &mut (*adder.c);
}
use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("{}", type_name::<T>());
}
fn main() {
    let mut farewell = "goodbye".to_owned();
    // 需要 mut, 否则调用diary() 失败, 所以这里可以推断出只实现FnMut(含FnOnce)
    let mut diary = || {
        // fnMut trait call_mut(&mut self)
        // 使用了可变借用
        &mut farewell;
        // 相当于 看drop 是 drop<T>(_x:T)
        // 所以这里也是传递 &mut String 类型,同理 ,无法实现Fn trait
        // drop( &mut farewell );
    };
    diary();
    diary();
}

4.3 Fn

源码定义
pub trait Fn<Args: Tuple>: FnMut<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
得出结论
  • 我们可以看到,实现了Fn trait的闭包,肯定也实现FnMut, FnOnce 这2个trait
  • 实现了Fn trait的闭包(实际就是三个trait全部实现)的闭包,调用时相当于FnMut::call(&self,args)
Tip
  • 那种不捕获任何值的闭包或最终只使用捕获变量的不可变借用的闭包,就是实现Fn trait的闭包
fn main() {
    let farewell = "goodbye".to_owned();
    let diary = move || {
        println!("I said {}.", farewell);
    };
    diary(); // ok
    diary(); // ok
}
fn apply<F>(f: F)
where
    F: Fn(),
{
    f();
}
// 也是实现了 Fn() trait
fn function() {
    println!("I'm a function!");
}
fn main() {
    let x = 7;
    let print = || {
        // 对x的不可变借用
        println!("{}", x);
    };
    apply(print);
    apply(function);
    println!("{}", x);
}

5 trait bound

实现了 FnMut trait的闭包用FnOnce() 做约束
// trait 是FnOnce , 表示传递的参数必须是实现了FnOnce的闭包
// 这里的写法 和一般的trait 不太一样
fn apply<F>(f: F)
where
    F: FnOnce(),
{
    f();
    // f(); 再次调用就报错了.
}
fn main() {
    let mut farewell = "goodbye".to_owned();
    let mut diary = || {
        farewell.push_str(" world");
        println!("{}", farewell);
    };
    diary(); // diary 实现FnMut (也实现了 FnOnce)
    apply(diary); // 可以传递给参数是闭包,类型为 FnOnce() 的函数
实现了 Fn trait的闭包用FnMut() 做约束
fn apply<F>(mut f: F)
where
    F: FnMut(),
{
    f();
}
fn main() {
    let x = "goodbye".to_owned();
    let diary = || {
        // 对x 不可变借用的捕获
        println!("{}", x);
    };
    // diary 闭包实现了全部trait
    diary();
    // 可以传递给 参数是闭包, 类型是 FnMut() 的函数
    apply(diary);
}

指定闭包的参数类型

fn apply<F>(f: F, v: i32)
where
    // 表示 传递的参数是闭包,然后必须携带一个i32的参数
    F: FnOnce(i32),
{
    f(v);
}

fn main() {
    let mut farewell = "goodbye".to_owned();
    let mut diary = |x| {
        println!("param: {}", x);
        farewell.push_str(" hello");
    };
    diary(22);
    diary(21);
    apply(diary, 33);
    println!("{}", farewell);
}
增加闭包的返回值
fn apply<F>(f: F, v: i32)
where
    // 表示 传递的参数是闭包,然后必须携带一个i32的参数,有返回值且类型是i32
    F: Fn(i32) -> i32,
{
    f(v);
}

fn main() {
    let diary = |x| {
        println!("{x}");
        x + 1
    };
    diary(22);
    diary(21);
    apply(diary, 33);
}
fn apply<F, T>(f: F, v: T)
where
    // 表示 传递的参数是闭包,然后必须携带一个类型是T的参数
    F: FnOnce(T),
{
    f(v);
}

fn main() {
    let mut farewell = "goodbye".to_owned();
    let mut diary = |x| {
        println!("param: {}", x);
        farewell.push_str(" hello");
    };
    //如果这里调用这个一下, 会将闭包的参数进行推断,变成&str了,
    diary("abc");
    apply(diary, " world");
    println!("{}", farewell);
}

6 闭包作为返回值

fn create_fn() -> impl Fn() {
    let text = "Fn".to_owned();
    // move 必须要有,要不然最后 text 会被 drop
    move || println!("This is a: {}", text)
}

fn create_fnmut() -> impl FnMut() {
    let text = "FnMut".to_owned();
    // 同样move
    move || println!("This is a: {}", text)
}

fn create_fnonce() -> impl FnOnce() {
    let text = "FnOnce".to_owned();

    move || println!("This is a: {}", text)
}

fn main() {
    let fn_plain = create_fn();
    let mut fn_mut = create_fnmut();
    let fn_once = create_fnonce();

    fn_plain();
    fn_mut();
    fn_once();
}

7 闭包的类型?

fn main() {
    let mut f = |x: i32| x + 1;
    // 会报错, 前面说过, 编译器会给闭包生成一个匿名的结构体
    // 就算闭包的参数和返回值一样, 也是不同的结构体, 所以是不同的类型
    f = |x: i32| x + 1;
}
Back to top