携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情
今天我们来学习数据结构系列中的最后一讲内容,闭包。闭包是函数编程语言中非常重要的一个工具,它可以作为参数传递给函数,也可作为返回值被函数返回。Rust 中的闭包与其他编程语言中的闭包都有所不同。接下来,我们将跟着老师一起,慢慢解开闭包的神秘面纱。
01-Rust 中的闭包
在 Rust 的官方文档中对闭包的定义是:
A closure expression produces a closure value with a unique, anonymous type that cannot be written out. A closure type is approximately equivalent to a struct which contains the captured variables. 译:闭包由闭包表达式产生,具有唯一、匿名的类型。闭包类型近似地等价于一个由它从上下文中捕获的变量组成结构体。
从上面的描述中,我们可以知道,Rust 中的闭包是一个类型(或者说结构体),且该类型无法在别的地方使用(匿名)。且闭包类型由所有从上下文(作用域)中捕获的变量组成。
Rust 中闭包表达式形如:|args| { codes },这种闭包以只读引用的形式捕获上下文中的变量;如果需要所有权转移到闭包中,可以使用 move |args| { codes }。
闭包类型这种特殊的结构体到底长什么样?考虑如下代码:
let name:String = String::from("rust");
let mut table = HashMap::new();
table.insert("hello", "world");
let c = move || println!("hello: {}, {:?}", name, table);
c 是一个闭包,它的类型结构体大概类似于:
struct anonymousC {
name: String,
table: HashMap<&str, &str>,
}
闭包是存储在栈上的,并且除了捕获的数据外,闭包本身不包含任何额外函数指针指向闭包的代码。
02-Rust 的闭包类型
Rust 中,声明闭包时并不需要指定闭包要满足的约束,但当闭包作为参数传递给函数或作为一个结构体的域时,需要对闭包的类型进行约束。接下来我们一起学习下 Rust 中的闭包类型。
02-Rust 的闭包类型
Rust 中,声明闭包时并不需要指定闭包要满足的约束,但当闭包作为参数传递给函数或作为一个结构体的域时,需要对闭包的类型进行约束。接下来我们一起学习下 Rust 中的闭包类型。
02.1-FnOnce
FnOnce 的定义如下:
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
从定义中可以看出:
- FnOnce trait 包含了一个关联类型 Output,表示闭包的输出类型;
- 包含一个未作限定的泛型参数 Args,它其实对应了闭包定义是 move |args| {codes} 中的 args;
- 定义了一个函数 call_once,需要额外注意的是,该方法的第一个参数是 self,会将当前闭包的所有权转移到 call_once 之内。所以 FnOnce 仅支持调用一次。
02.2-FnMut
FnMut 的定义如下:
pub trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(
&mut self,
args: Args
) -> Self::Output;
}
从上面的定义中可以看出:
- FnMut “继承”了 FnOnce,或者说 FnOnce 是它的 super trait,这也就意味着,任何需要 FnOnce 的地方,都能传一个 FnMut 进去。
- FnMut 多定义了一个方法 call_mut,注意,它的第一个参数是 &mut self,意味着它不会移动闭包结构体的所有权,也就可以调用多次。
02.3-Fn
Fn 的定义如下:
pub trait Fn<Args>: FnMut<Args> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
从上面的定义可以看出:
- Fn “继承“了 FnMut。
- Fn 定义了额外的方法 call,它的第一个参数是 &self,即不会取得所有权,且不会修改闭包捕获变量的内容,可以调用多次。
02.4-闭包使用场景
Rust 中闭包使用场景主要包括:
-
作为函数的参数传递,例如 Iterator 中的 map 方法,接收的就是 FnMut 参数:
fn map<B, F>(self, f: F) -> Map<Self, F> where Self: Sized, F: FnMut(Self::Item) -> B, { Map::new(self, f) } -
作为函数的返回值
-
Rust 中可以为闭包实现某个 trait,使其表现出其他行为。例如 tonic 中的示例:
pub trait Interceptor { /// Intercept a request before it is sent, optionally cancelling it. fn call(&mut self, request: crate::Request<()>) -> Result<crate::Request<()>, Status>; } impl<F> Interceptor for F where F: FnMut(crate::Request<()>) -> Result<crate::Request<()>, Status>, { fn call(&mut self, request: crate::Request<()>) -> Result<crate::Request<()>, Status> { self(request) } }
本节课程链接:《19|闭包:FnOnce、FnMut和Fn,为什么有这么多类型?》
历史文章推荐