前言
近日在学习完rust基础知识后开始学习网络编程相关的知识,在实现简单的多线程web server时,发现其中包含了大量闭包相关知识,所以进行课后的补充学习并作记录
参考:b站令狐一冲rust进阶
一、closure闭包是什么?
闭包是可以保存进变量或者是作为参数传递给其他函数的匿名函数,闭包和函数不同的是,closure允许捕获调用者作用域中的值
定义比较抽象,直接根据代码学习更为直观
二、使用closure
1.定义闭包
代码如下:
//闭包的完整定义:
let add_one_v2 = |x:u32| -> u32 {x+1};
//闭包定义会为每个参数和返回值推导一个具体的类型
//但是不能推导两次
//简化定义:
let add_one_v3 = |x| {x+1};
let add_one_v4 = |x| x+1;
闭包其实类似于函数的定义,在此定义相似作用的函数作对比:
fn add_one_v1 (x:u32) -> u32 {
x+1
}
此外,针对不能推导两次类型也给出例证:
//不能推导两次的例子:
let example_closure = |x| x;
let a = example_closure("okokok".to_string());
println!("{}",a);
let b = example_closure(5);
println!("{}",b);
//上述例子中,在为a赋值时
//闭包example_closure()进行了第一次类型推导
//参数类型和返回值类型都是String类型
//在为b赋值时
//闭包example_closure()进行了第二次类型推导
//参数类型和返回值类型都是integer类型
//在编译时发生报错
//为b赋值时期望参数x是一个String类型
//但是传入的是integer类型
//所以闭包可以自行推断自己的参数和返回值类型,
//但是不能判断两次
此外,定义中提到闭包可以捕获调用者作用域中的值
示例如下:
//捕获作用域中的值
let i = 4;
let exe = |x| x+i;
let test = exe(5);
println!("{}",test);
可见在作用域中定义的整型变量 i 可以直接在闭包中使用,验证了上述“闭包可捕获调用者作用域中值”的说法
2.使用闭包
关于闭包的 常规使用 和 闭包捕获作用域中值 已经在上文给出简单示例,这里就不多作赘述,接下来以一个实现缓存功能的例子进行说明,示例如下:
//首先定义一个结构体
//->前置知识:泛型,trait
struct Cacher<T>
where T:Fn(u32) -> u32
//定义T的trait bound,指定类型T必须拥有如上所述的闭包特性
{
calculation:T,
//这里相当于在结构体中内置了一个闭包
value:Option<u32>,
}
impl<T> Cacher<T> where T:Fn(u32) -> u32 {
fn new(calculation:T) -> Cacher<T> {
//初始化结构体
Cacher {
calculation,
//结构体知识:当拥有同名变量时可以直接定义
value:None,
}
}
fn value(&mut self, arg:u32) -> u32 {
//此处实现了缓存的功能
//如果结构体中的value值为空
//则传入一个经过内置闭包计算后的值并保存
//如果结构体中的value值不为空
//则保持原来的值不变并将初始值传回
match self.value {
Some(v) => v,
//初始值不为空,将初始值传回
None => {
//初始值为空
let v = (self.calculation)(arg);
//将传入的新值经过内置闭包计算
self.value = Some(v);
//写入结构体中(即写入缓存)
v
//传回写入实际值
},
}
}
}
接下来定义main函数进行测试:
fn main() {
let calculation = |x| x+1;
let mut tmp = Cacher::new(calculation);
//注意这里要将结构体定义为可变类型
let v1 = tmp.value(1);
//此时结构体中value为空
//所以会写入经过内置闭包计算的新值并传回
println!("v1 is {}",v1);
let v2 = tmp.value(2);
//此时结构体中已经有value值
//所以不会写入新值,只会将初始值传回
println!("v2 is {}",v2);
println!("Hello, world!");
}
结果如下:
Compiling learn_closure2 v0.1.0 (/root/learn_rust/learn_closure2)
Finished dev [unoptimized + debuginfo] target(s) in 1.58s
Running `target/debug/learn_closure2`
v1 is 2
v2 is 2
Hello, world!
由编译运行结果可见一斑
3.再述捕获作用域值
闭包可以通过三种方式捕获其环境,分别对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用
笔者在经过自己的一些尝试后发现,如果不明确声明move,则rust编译器默认对捕获的值是不可变借用,而可变借用的实验笔者经过几次尝试后并未成功,准备之后再作探讨。所以以下仅记录实验成功的示例,日后在做补充。
首先是简单的对具有copy trait的变量进行捕获测试:
let x = 3;
let equal_to_x = |z| z==x;
let z1 = 3;
assert!(equal_to_x(z1));
测试结果如下:
Compiling learn_closure3 v0.1.0 (/root/learn_rust/learn_closure3)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/learn_closure3`
而对于没有copy trait的变量,如String类型,也做了类似实验,代码如下:
let x = "123".to_string();
let equal_to_x = |z| z==x;
println!("{}",x);
let z2 = String::from("123");
assert!(equal_to_x(z2));
测试结果如下:
Compiling learn_closure3 v0.1.0 (/root/learn_rust/learn_closure3)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/learn_closure3`
123
由x在经过捕捉后在main作用域内依然可以生效可以认为此处的默认捕获为不可变引用
而若是声明了此处为 move 如下所示:
let x = "123".to_string();
let equal_to_x = move |z| z==x;
println!("{}",x);
let z2 = String::from("123");
assert!(equal_to_x(z2));
测试结果如下:
Compiling learn_closure3 v0.1.0 (/root/learn_rust/learn_closure3)
error[E0382]: borrow of moved value: `x`
--> src/main.rs:11:19
|
9 | let x = "123".to_string();
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait
10 | let equal_to_x = move |z| z==x;
| -------- - variable moved due to use in closure
| |
| value moved into closure here
11 | println!("{}",x);
| ^ value borrowed here after move
For more information about this error, try `rustc --explain E0382`.
可以发现出现了经典的 “value borrowed here after move” 报错,说明所有权已经被捕获到闭包内。
总结
如上就是我对于闭包的一些学习记录,而对于捕获类型会在之后继续学习探讨,如有错误,还请各位读者不吝批评赐教