闭包 Closure
闭包时一种匿名函数,它可以赋值给一个变量,也可以作为参数传递给其他函数
不同的是闭包可以捕获调用者作用域中的值
1.基本使用
闭包的基本语法如下
|param1, param2,...|{
// 函数体
返回值
}这么一看,闭包函数和普通函数大差不差,参数列表不是用小括号(),而是用||
函数内部也是和普通函数一样,正常书写函数体,最后一行若没有;,就视为返回值
当闭包只有一个返回值时,可以省略{}
|param1, param2,...| 返回值闭包可以赋值给一个变量,然后可以通过这个变量来调用闭包函数
let print = |str| {
println!("{}", str);
};
print("你好"); // 打印你好
// 使用返回值
let get_value = |value| value;
let val = get_value(123);
println!("{}", val) // 1232.闭包的类型推导
在书写普通函数时,我们需要给每一个参数的类型显示声明,因为普通函数通常需要作为Api可供外部使用
但是闭包函数不会作为Api最外提供,因此闭包函数享受编译器的类型推导能力
所以闭包函数的参数类型和返回值类型无需显式添加
一般我们写个函数可以这样
let sum = |x: i32, y: i32| -> i32 {
x + y
}为了方便,可以简化为这样
let sum = |x, y| x + y;但是要注意,类型推导不是泛型,当编译器推导出一种类型后,它就会一直使用该类型:
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5); // 此处会报错上面代码中,第一次调用example_closure后,rust已经固定参数类型为String了,所以再次调用时传入一个整型,就会报错.
3.捕获作用域中的值
闭包可以捕获作用域中的值,例如
fn main() {
let x = 4;
let capture = || {
println!("{}", x);
};
capture()
}上面代码中,x不是属于capture函数中的值,但是在函数中也是可以使用,
这对于普通函数来说是不可以的
fn main() {
let x = 4;
fn capture() {
println!("{}", x); // 这里会报错
}
capture()
}当闭包函数中使用了一个变量,会现在闭包函数的当前作用域去寻找,若找不到就会往上层作用域找,如果找到顶层作用域都没找到,则会给你报错
const PI: f32 = 3.14;
fn main() {
let x = 1;
{
let y = 2;
{
let z = 3;
let capture = || {
println!("{},{},{},{}", PI, x, y, z); // 3.14,1,2,3
// println!("{}", a); // 这里会报错,因为a不存在
};
capture()
}
}
}4.闭包对内存的影响
当闭包从环境中捕获一个值时,会分配内存去存储这些值。对于有些场景来说,这种额外的内存分配会成为一种负担。与之相比,函数就不会去捕获这些环境值,因此定义和使用函数不会拥有这种内存负担。
5.闭包特性
闭包一共有三种trait特性可以实现,区别在于对作用与变量的捕获方式,恰好对应函数参数的三种传入方式:转移所有权、可变借用、不可变借用
FnOnce: 只需要捕获所有权,闭包只能调用一次。FnMut: 需要修改捕获的变量,闭包可以多次调用。Fn: 不需要修改捕获的变量,闭包可以多次调用。
5.1 FnOnce
FnOnce的核心特征是只能被调用一次,并且在该闭包内会拿走被捕获变量的所有权
捕获所有权意味着变量从原始作用域移动(move)到了闭包内部
fn fn_once<F>(func: F)
where
F: FnOnce(usize) -> bool,
{
println!("{}", func(3));
// println!("{}", func(4)); // 报错,func只能被使用一次
}
fn main() {
let x = vec![1, 2, 3];
fn_once(|z| {
let v = x; // x被移动,所有权给了v
let len = v.len();
z == len
});
// println!("{:?}", x) // 报错,x已被移动
}5.2 FnMut
FnMut以可变借用的方式捕获了环境中的值,因此可以修改该值:
fn main() {
let mut s = String::new();
let update_string = |str| s.push_str(str);
update_string("hello");
println!("{:?}",s);
}这么写编译器会报错,因为闭包默认是实现的Fn
想要在闭包内部捕获可变借用,需要把该闭包声明为可变类型,这样闭包会自动推导为FnMut
fn main() {
let mut s = String::new();
let mut update_string = |str| s.push_str(str);
update_string("hello");
println!("{:?}",s);
}当FnMut的闭包作为函数的参数时,需要给参数添加mut关键字
fn fn_once<F>(mut func: F)
where
F: FnMut(),
{
func();
}
fn main() {
let mut s = String::from("hello");
fn_once(|| {
s.push_str("world");
});
println!("{}", s); // helloworld
}TIP
只要闭包捕获的类型都实现了Copy特征的话,这个闭包就会默认实现Copy特征。
我们来看一个例子:
let s = String::new();
let update_string = || println!("{}",s);这里取得的是s的不可变引用,所以是能Copy的。而如果拿到的是s的所有权或可变引用,都是不能Copy的。我们刚刚的代码就属于第二类,取得的是s的可变引用,没有实现Copy。
// 拿所有权
let s = String::new();
let update_string = move || println!("{}", s);
exec(update_string);
// exec2(update_string); // 不能再用了
// 可变引用
let mut s = String::new();
let mut update_string = || s.push_str("hello");
exec(update_string);
// exec1(update_string); // 不能再用了5.3 Fn
Fn 特征以不可变借用的方式捕获环境中的值,一个闭包默认实现Fn特征
let s = String::from("hello");
let update_string = || {
println!("{}", s); // hello 不可变借用
};
update_string();
println!("{}", s); // hello 仍可以使用5.4 三种Fn的关系
实际上,一个闭包并不仅仅实现某一种 Fn 特征,规则如下:
- 所有的闭包都自动实现了
FnOnce特征,因此任何一个闭包都至少可以被调用一次 - 没有移出所捕获变量的所有权的闭包自动实现了
FnMut特征 - 不需要对捕获变量进行改变的闭包自动实现了
Fn特征
fn main() {
let s = String::new();
let update_string = || println!("{}",s);
exec(update_string);
exec1(update_string);
exec2(update_string);
}
fn exec<F: FnOnce()>(f: F) {
f()
}
fn exec1<F: FnMut()>(mut f: F) {
f()
}
fn exec2<F: Fn()>(f: F) {
f()
}6.闭包在函数中的应用
6.1 作为参数
闭包作为参数,这种写法是经常见到的,通常作为回调函数使用
注意闭包作为参数时要使用泛型,以下代码中,有一个F泛型,泛型约束为FnOnce(String),也就是说我们的闭包函数是FnOnce的,并且有一个String类型参数,然后在参数列表中给闭包变量设置这个F泛型
fn get_hello<F: FnOnce(String)>(f: F) {
f("hello".to_string());
}
fn main() {
get_hello(|x| println!("{}", x))
}6.2 作为返回值
闭包还可以作为函数的返回值。由于闭包是匿名的,我们需要使用 impl Trait 或 Box 来描述其类型。
使用 impl Fn 返回闭包
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |y| x + y
}
fn main() {
let add_five = make_adder(5);
println!("5 + 3 = {}", add_five(3)); // 输出: 5 + 3 = 8
}使用 Box<dyn Fn> 返回闭包
fn make_adder(x: i32) -> Box<dyn Fn(i32) -> i32> {
Box::new(move |y| x + y)
}
fn main() {
let add_ten = make_adder(10);
println!("10 + 2 = {}", add_ten(2)); // 输出: 10 + 2 = 12
}6.move关键字
move关键字可以让一个闭包捕获变量时
- 对于非
Copy类型,强制获取所有权,如String,Vec - 对于实现
Copy的类型,进行复制,如i32,bool
我们在Fn类型的闭包中试试
fn main() {
let x = 42; // i32,实现了 Copy
let s = String::from("hello"); // String,未实现 Copy
let closure = || {
println!("数字: {}, 字符串: {}", x, s);
};
closure();
println!("{}", x); // x ,仍可以被使用
println!("{}", s); // hello ,仍可以被使用
}fn main() {
let x = 42; // i32,实现了 Copy
let s = String::from("hello"); // String,未实现 Copy
let closure = move || {
println!("数字: {}, 字符串: {}", x, s);
};
closure();
println!("{}", x); // x ,仍可以被使用
// println!("{}", s); // 错误,s的所有权已被转移
}以上代码中的闭包默认是Fn类型的,我们再试试FnOnce
fn do_somthing<T>(f: T)
where
T: FnOnce(),
{
f();
}
fn main() {
let x = 42; // i32,实现了 Copy
let s = String::from("hello"); // String,未实现 Copy
do_somthing(|| {
println!("x:{},s:{}", x, s);
});
println!("x:{}", x); // 42
println!("s:{}", s); // hello
}fn do_somthing<T>(f: T)
where
T: FnOnce(),
{
f();
}
fn main() {
let x = 42; // i32,实现了 Copy
let s = String::from("hello"); // String,未实现 Copy
do_somthing(move || {
println!("x:{},s:{}", x, s);
});
println!("x:{}", x); // 42
// println!("s:{}", s); // 错误,s所有权已移动
}在FnMut中也是一样
fn do_somthing<T>(mut f: T)
where
T: FnMut(),
{
f();
}
fn main() {
let x = 42; // i32,实现了 Copy
let s = String::from("hello"); // String,未实现 Copy
do_somthing(|| {
println!("x:{},s:{}", x, s);
});
println!("x:{}", x); // 42
println!("s:{}", s); // hello
}fn do_somthing<T>(mut f: T)
where
T: FnMut(),
{
f();
}
fn main() {
let x = 42; // i32,实现了 Copy
let s = String::from("hello"); // String,未实现 Copy
do_somthing(move || {
println!("x:{},s:{}", x, s);
});
println!("x:{}", x); // 42
// println!("s:{}", s); // 错误,s所有权已移动
}