Rust中的Fn、FnOnce、FnMut闭包详解
闭包会自动实现Fn
、FnOnce
、FnMut
这三个trait中的任意一个,这取决于闭包在调用过程中对捕获变量的使用;
先了解Rust中的借用规则:
- 在给定的作用域内,可以有多个不可变引用;
- 在给定的作用域内,只允许有一个可变引用;
- 在给定的作用域内,不允许同时存在可变引用和不可变引用;
Fn 闭包
如果闭包捕获了外部环境中的变量,并且在闭包内部仅对这些变量进行不可变访问,则闭包会自动实现Fn
trait;
实现了Fn
trait的闭包能被多次调用;
fn main() {
let str = "hello".to_string();
let closure = || {
println!("{}",str);
};
call_closure(closure);
call_closure(closure);
call_closure(closure);
call_closure(closure);
println!("{}",str);
}
fn call_closure(f: impl Fn()){f();}
这段代码中,闭包内部只对捕获到的变量str
进行了不可变访问,所以它将自动实现Fn
trait;
根据借用规则,允许存在多个str
的不可变引用,所以这个闭包能被多次调用;
FnOnce 闭包
如果闭包捕获了外部环境中的变量,并且在闭包内部对其中至少一个变量进行了所有权转移,则闭包会自动实现FnOnce
trait;
实现了FnOnce
trait的闭包只允许被调用一次;
fn main() {
let str = "hello".to_string();
let closure = move || {
let str2 = str;
println!("{}",str2);
};
call_closure(closure);
//call_closure(closure); //只允许调用一次
//println!("{}",str); //所有权被转移,无法再访问
}
fn call_closure(f: impl FnOnce()){f();}
这段代码中,闭包内部将捕获变量str
的所有权转移到了str2
;move
关键字表示闭包会获取捕获变量的所有权,而不仅仅只是引用他们;
由于该闭包发生了捕获变量的所有权转移,所以它会自动实现FnOnce
trait;
由于str
的所有权被转移,str
将被标记为不可使用,这也是FnOnce
闭包只能被调用一次的原因;
FnMut 闭包
如果闭包捕获了外部环境中的变量,并且在闭包内部对其中至少一个变量进行了可变访问,则闭包会自动实现FnMut
trait;
实现了FnMut
trait的闭包允许被多次调用;
fn main() {
let mut str = "hello".to_string();
let mut closure = || {
str.push_str(" world");
println!("{}",str);
};
//println!("{}",str); //不可访问
call_closure(&mut closure);
call_closure(&mut closure);
call_closure(&mut closure);
println!("{}",str); //可以访问
}
fn call_closure(f: &mut impl FnMut()){f();}
这段代码中,闭包内部发生了对捕获变量str
的可变访问,该闭包将自动实现FnMut
trait;
第9
行代码中,str
不可变引用与闭包中捕获变量str
的可变引用作用域重叠,违反借用规则;
第15
行代码中,str
不可变引用与闭包中捕获变量str
的可变引用作用域没有重叠,没有违反借用规则;