Loading

Rust闭包

很多语言中都有闭包的概念,闭包就是一个能够捕获周围作用域中变量的函数,它们通常以简洁的形式展现,比如lambda表达式。

Rust的Lambda表达式

Rust中的闭包也是lambda表达式形式的,先来说一下Rust中lambda的基本格式:

|参数列表| -> 返回值 {
    语句1;
    语句2;
    语句3;
    ...
    表达式  // 最后一个表达式作为返回值
}
  1. 在lambda只有一句时,括号可以省略
  2. 返回值可以省略,由编译器自动推测
  3. 参数类型可以省略,由编译器自动推测

下面是一个返回一个数的二倍的lambda表达式:

|x| x * 2

闭包的三种变量捕获方式

上面那个例子,严格意义上来说不能算闭包,因为它没有捕获周围作用域的变量,它只用到了外部传入的参数x

在Rust中,所有变量被所有权体系管控,即使被闭包捕获,它们也必须遵从这套体系,所以,根据捕获变量方式的不同衍生出了三种闭包。

不可变借用(Fn)

let x = String::from("a variable");
let print = || println!("{}", x);

上面的闭包print中引用了周围的变量x,由于它并没有修改x,所以,实际是x的不可变引用被借用给了闭包。这种闭包在Rust中的类型为Fn

你不能在捕获了变量x的Fn类型闭包的最后一次使用之前创建变量x的可变引用或修改x的值,这是所有权系统的限制

可变借用(FnMut)

let mut x = String::from("a variable");
let mut push = || {
    x.push_str("123456");
};

上面的闭包push中修改了x,所以,x的可变引用被借用给了闭包,同时,由于闭包每次调用的内部状态也发生了改变,你必须把push也声明成mut。这种闭包的在Rust中的类型为FnMut

你不能在捕获了变量x的Fn类型闭包的最后一次使用之前创建变量x的任何引用或访问x的值,这是所有权系统的限制

获取所有权(FnOnce)

let movable = Box::new(3);

let consume = || {
    println!("movable : {:?}", movable);
    mem::drop(movable);
};

上面的闭包consume中,由于mem::drop函数需要获取参数的所有权,所以,movable被移动到闭包中,它的所有权也归闭包所有,闭包再把它移动给mem::drop

所以,在调用consume之后,无法再次调用,因为movable的所有权已经不在了。这种闭包在Rust中的类型为FnOnce

编写类型说明

上面我们说了,Rust中有三种类型的闭包,FnFnMutFnOnce,但是我们从没编写过这些类型声明,因为在上面的场景中编写类型说明其实也没啥大用。

但在将闭包作为其它函数的参数时,我们必须为其编写类型说明:

fn apply<F>(f: F) where F: Fn() {
    f();
}

apply(|| println!("I am a closure"));

Rust的trait不像Java的接口,可以直接作为多态类型来使用,Rust只能通过泛型的方式来描述一个参数具有某个trait

你也可以将闭包定义成FnOnce,但它并不表示该闭包一定必须要获取外部变量的所有权:

fn apply<F>(f: F) where F: FnOnce() {
    f();
}

// 实际上该闭包什么也没获取
apply(|| println!("I am a closure"));

简单来说,闭包的类型描述了它捕获外部变量能力的上界,如类型为FnOnce的闭包,其内部可以通过&T(不可变借用)、&mut T(可变借用)和T(移动所有权)的方式来捕获外部变量,而FnMut则不能通过T(移动所有权)的方式捕获外部变量。

fn apply<F>(f: F) where F: Fn() {
    f();
}

// x是一个外部变量,这里会出错,因为apply中定义的闭包是`Fn`类型,这里却尝试在闭包中获取外部变量的所有权
apply(|| mem::drop(x));

函数作为参数

普通函数也可以作为参数,但与闭包不同的是,它不可以捕获外部变量。

fn apply<F>(f: F) where F: Fn() {
    f();
}

fn function() {
    println!("I am closure");
    // println!("{}", &x); 错误的代码,因为函数不能捕获外部变量
}

apply(function);

闭包作为返回值

Rust的返回值必须是具体类型,不能是泛型,所以,可以这样返回闭包:

fn return_closure() -> impl Fn() {
    || println!("Hi, i returned a closure to you!")
}
return_closure()();

同理,也应该可以这样使用闭包作为参数:

fn receive_closure(f: impl Fn()) {
    f();
}
receive_closure(||println!("{}", &x));
posted @ 2022-11-27 19:47  yudoge  阅读(371)  评论(0编辑  收藏  举报