RUST学习记录 -> 闭包


前言

近日在学习完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” 报错,说明所有权已经被捕获到闭包内。


总结

如上就是我对于闭包的一些学习记录,而对于捕获类型会在之后继续学习探讨,如有错误,还请各位读者不吝批评赐教

猜你喜欢

转载自blog.csdn.net/weixin_45704680/article/details/121094813
今日推荐