Rust闭包
很多语言中都有闭包的概念,闭包就是一个能够捕获周围作用域中变量的函数,它们通常以简洁的形式展现,比如lambda表达式。
Rust的Lambda表达式
Rust中的闭包也是lambda表达式形式的,先来说一下Rust中lambda的基本格式:
|参数列表| -> 返回值 {
语句1;
语句2;
语句3;
...
表达式 // 最后一个表达式作为返回值
}
- 在lambda只有一句时,括号可以省略
- 返回值可以省略,由编译器自动推测
- 参数类型可以省略,由编译器自动推测
下面是一个返回一个数的二倍的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中有三种类型的闭包,Fn
、FnMut
和FnOnce
,但是我们从没编写过这些类型声明,因为在上面的场景中编写类型说明其实也没啥大用。
但在将闭包作为其它函数的参数时,我们必须为其编写类型说明:
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));