闭包
闭包是一种匿名函数,它可以赋值给变量,也可以作为参数传递给其它函数。不同于函数的是,它允许捕获调用者作用域中的值,如:
fn main() {
let x = 1;
let sum = |y| x + y;
assert_eq!(3, sum(2));
}
在上述代码中,闭包sum
使用了作用域外的值x
。
与函数相比,由于闭包不会暴露给外界调用,因此在绝大多数情况下,在闭包中,无需指定传入参数与传出参数的类型,编译器会自行推断。
闭包格式:
|param1, param2, ...| {
语句1;
语句2;
返回表达式
}
闭包的类型签名:
struct Cacher<T>
where
T: Fn(u32) -> u32,
{
query: T,
value: Option<u32>,
}
T: Fn(u32) -> u32
表示 T
类型必须实现一个闭包特征。该闭包有一个u32
参数,返回一个u32
类型的值。
闭包对内存的影响
当闭包从环境中捕获一个值时,会分配内存去存储这些值。对于有些场景来说,这种额外的内存分配会成为一种负担。与之相比,函数就不会去捕获这些环境值,因此定义和使用函数不会拥有这种内存负担。
三种 Fn 特征
闭包捕获变量有三种途径,恰好对应函数参数的三种转入方式:转移所有权、可变借用、不可变借用。
FnOnce
对应转移所有权,该闭包只能运行一次:
fn fn_once<F>(func: F)
where
F: FnOnce(usize) -> bool,
{
println!("{}", func(3));
println!("{}", func(4));
}
fn main() {
let x = vec![1, 2, 3];
fn_once(|z|{z == x.len()})
}
所有的闭包都自动实现了 FnOnce
特征。
FnMut
对应可变借用:
fn main() {
let mut s = String::new();
let mut update_string = |str| s.push_str(str);
exec(update_string);
println("{:?}", s);
}
fn exec<'a, F: FnMut(&'a str)>(mut f: F) {
f("hello")
}
没有移出所捕获变量的所有权的闭包自动实现了 FnMut
特征。
Fn
对应不可变借用:
fn main() {
let s = "hello, ".to_string();
let update_string = |str| println!("{},{}", s, str);
exec(update_string);
println!("{:?}", s);
}
fn exec<'a, F: Fn(String) -> ()>(f: F) {
f("world".to_string())
}
不需要对捕获变量进行改变的闭包自动实现了 Fn
特征。
move
move
关键字可以强制闭包取得捕获变量的所有权。这种用法通常用于闭包的生命周期大于捕获变量的生命周期时,例如将闭包返回或移入其他线程。
fn main() {
let s = String::new();
let update_string = move || println!("{}", s);
exec(update_string);
}
fn exec<F: FnOnce()>(f: F) {
f()
}
Fn
决定闭包如何使用被捕获的变量move
决定如何捕获变量
在 Rust 中,闭包的行为取决于它所捕获的变量。如果闭包捕获的是一个可复制类型(如 String),则它会对其进行复制,而不是移动。
这是因为闭包需要对其捕获的变量进行所有权管理,以确保在闭包执行期间它们仍然有效。对于可复制类型,复制是最简单的管理方式。
需要注意的是,如果想要在闭包中移动一个值,可以使用 move 关键字来强制将其移动到闭包中。在这种情况下,闭包将获取变量的所有权,并且该变量将不再可用于闭包之外。
闭包作为函数返回值
将闭包作为函数返回值时,可以返回一个特征对象(要求实现该闭包特征)。
fn main() {
let f = factory(2);
let answer = f(2);
assert_eq!(7, answer);
println!("Hello, world!");
}
fn factory(x: i32) -> Box<dyn Fn(i32) -> i32> {
let num = 5;
if x > 1{
Box::new(move |x| x + num)
} else {
Box::new(move |x| x - num)
}
}