前言
前面的文章中我们讲了几个 Rust 的基础概念,本篇文章主要讲讲 Rust 的闭包,让我们来进一步去学习 Rust 中的核心知识点吧。
闭包是什么?
在Rust中,闭包是一种特殊的函数类型,允许你在代码中定义匿名函数。闭包是一种可以捕获上下文变量的函数,它可以在定义时访问定义范围内的变量,并且可以在之后被调用多次。
闭包能干什么?
- 灵活性: 闭包允许你在需要时直接定义和使用函数,而无需为其命名。
- 捕获上下文:闭包可以捕获其定义范围内的变量,使其可以在闭包内部使用,甚至在离开定义范围后仍然有效。
- 函数指针替代:闭包可以用作函数指针的替代品,方便地传递和使用。
闭包和函数的区别
- 语法:闭包的语法更为灵活,可以直接在代码中定义,而函数需要使用
fn
关键字进行声明。 - 捕获上下文: 闭包可以捕获其定义范围内的变量,函数则需要显式传递参数。
- 匿名性: 闭包可以是匿名的,而函数总是有一个名字。
闭包有哪些类型?
在Rust中,有三种主要的闭包类型:
- Fn: 闭包可以访问其闭包环境中的值,并对其进行只读操作,它是多线程安全的。
- FnMut: 闭包可以访问其闭包环境中的值,并对其进行读写操作,它是非多线程安全的,需要做一些限制。
- FnOnce: 闭包只能从其闭包环境中获取值一次,通常在闭包执行后会消费这些值。
包含关系: FnOnce 包含 FnMut、FnMut 包含 Fn。对应 Trait 的源码是这样的:
rust
pub trait Tuple {}
pub trait FnOnce<Args: Tuple> {
type Output;
fn call_once(self, args: Args) -> Self::Output;
}
pub trait FnMut<Args: Tuple>: FnOnce<Args> {
fn call_mut(&mut self, args: Args) -> Self::Output;
}
pub trait Fn<Args: Tuple>: FnMut<Args> {
fn call(&self, args: Args) -> Self::Output;
}
也就是说如果你把一个闭包的类型声明为 FnMut,那么它也能同时接收 Fn 的闭包。
闭包性能怎样?
Rust 闭包的性能非常好,比函数指针都要快,别的语言使用闭包一般都需要在堆中分配内存并且进行垃圾回收,而 Rust 不会把闭包分配到堆内存中,除非你把它放到 Box 或其它堆内存容器中,Rust 闭包在被调用时会直接进行内联以此来提升执行性能(可以消除函数调用的开销)。Rust 的闭包会在运行时就确定类型,而比如 JavaScript 的闭包会在运行时才确定类型,这可以减少运行时的性能损耗。
闭包的使用例子
rust
fn main() {
let x = 5;
let y = 10;
// Fn 闭包,只读访问上下文变量
let read_only = |num: i32| -> i32 { num + x };
// Fn Closure Result: 15
println!("Fn Closure Result: {}", read_only(y));
// FnMut 闭包,可读写访问上下文变量
let mutable = |num: i32| -> i32 { x + y + num };
// FnMut Closure Result: 30
println!("FnMut Closure Result: {}", mutable(15));
// FnOnce 闭包,使用 move 在闭包内获取上下文变量所有权(Ownership)
let owned = move |num: i32| -> i32 { num * y };
// FnOnce Closure Result: 50
println!("FnOnce Closure Result: {}", owned(x));
// 尝试访问已经被 FnOnce 闭包获取所有权的变量,这会导致编译错误
// println!("Trying to use x after FnOnce: {}", x);
}
我们定义了一个使用不同类型闭包的示例:
Fn
闭包对上下文变量进行只读访问。FnMut
闭包对上下文变量进行读写访问。FnOnce
闭包获取上下文变量的所有权(Ownership),并在闭包内部消费了这个所有权。
闭包的所有权
- 一个没有修改外部变量的非 move 闭包既能进行 Copy 也能进行 Clone,不会发生所有权转移
- 会修改外部值的非 move 闭包既不能 Copy,也不能进行 Clone,当闭包变量赋值给另一个变量的时候,会发生所有权的转移
- 对于 move 闭包,如果闭包内捕获的内容都能 Copy,那闭包也能 Copy,如果闭包内捕获的内容都能 Clone,那闭包也能 Clone。
闭包实战
rust
use std::collections::HashMap;
struct Request {
method: String,
url: String,
body: Vec<u8>,
}
struct Response {
status_code: u32,
body: Vec<u8>,
}
// 用于存放 Rust 闭包,使用动态派发,即在运行时确定具体的闭包类型
type BoxedCallback = Box<dyn Fn(&Request) -> Response>;
struct Router {
routes: HashMap<String, BoxedCallback>,
}
impl Router {
fn new() -> Self {
Router {
routes: HashMap::new(),
}
}
// 可以添加一个路由,接收一个闭包。
fn add_route<C>(&mut self, url: &str, callback: C)
where
C: Fn(&Request) -> Response + 'static,
{
self.routes.insert(url.to_string(), Box::new(callback));
}
// 用来发送模拟请求
fn handle_request(&self, request: &Request) -> Response {
match self.routes.get(&request.url) {
// 找到了对应的方法,进行调用
Some(callback) => callback(request),
// 找不到对应的方法,返回 404
None => Response {
status_code: 404,
body: vec![],
},
}
}
}
fn main() {
// 创建一个路由管理器
let mut router = Router::new();
// 第二个参数传递一个闭包
router.add_route("/login", |req| {
println!(
"接收到 login 请求,请求体: {:?},请求方法: {}",
&req.body, req.method
);
Response {
status_code: 200,
body: vec![],
}
});
let login_request = Request {
body: vec![],
method: "POST".to_string(),
url: "/login".to_string(),
};
// 发送一个模拟请求,执行后控制台打印:
// 接收到 login 请求,请求体: [],请求方法: POST
router.handle_request(&login_request);
}
总结
闭包是Rust中强大的概念,提供了灵活、高效的函数式编程方式。通过使用不同的闭包类型,你可以根据需要选择只读、可读写或所有权转移的语义。这篇文章把 Rust 中闭包的所有重要内容都列举出来了,希望能对读者们有帮助。码字不易,内容基本都是纯手打的,很多都是自己在学习过程中记录下来的笔记和心得总结,点点赞点点关注就是对我最大的帮助~感谢