Rust编程·高级概念(2)

 

闭包

  • 闭包

闭包

  • 闭包是把代码和环境一起存储的数据结构
    • 闭包会引用(捕获/Move)环境中的自由变量
    • 成为闭包类型的一部分
    • 闭包中有自由变量和局部变量两类
  • 函数签名的解释

      fn spwn<F, T>(f: F) -> JoinHandle<T>
          where F: FnOnce() -> T,
              F: Send + 'static,
              T: Send + 'static,
    
    • F: FnOnce()->T F是一个闭包类型
    • Send 修饰的类型的所有权可以在线程间安全的发送
    • 'static修饰的类型
      • 拥有静态生态周期
      • 或拥有所有权的类型
    • F: Send + 'static
      • 这个闭包有静态生命周期或者有所有权
      • 代码是静态生命周期
      • 被捕获的变量也必须是静态生命周期或有所有权
      • 所以thread::spawn才要move关键字修饰

闭包是什么

  • 闭包是一个匿名类型
    • 声明后就会生成一个新的类型(与代码关联)
    • 且会包含捕获(Ref/Move)的变量
  • 闭包捕获的变量
    • 先是引用
    • 有move则移动所有权
    • 而且闭包的大小,只与捕获的变量有关
    • 与参数和局部变量无关
      • 因为它们是调用时在栈上分配的
      • 不会放到闭包结构体中
    • 捕获变量的位置

      let c = move || println!("name: {:?}, map: {:?}", name, hashmap);
      

      这个闭包的自由变量,相当于

      struct Closure {
          name: String,
          hashmap: HashMap<&str, &str>,
      }
      

      结构体中的顺序和捕获顺序一样,但编译器会自动优化变量的顺序来减少空间

  • 闭包只包含自由变量,却没有包含指向代码的函数指针

不同语言的闭包设计

  • 闭包捕获了环境中的变量,舍不得被捕获的变量的归属和生命周期变复杂
  • 所以其他语言不能将它们放在栈上,跟随函数自动消除
  • 必须要申请额外堆空间、动态分发(处理成函数指针)、额外的内存回收
  • 所以多数语言的闭包开销很大

Rust的闭包

  • 不用move时,只引用环境变量
    • 只要编译通过,说明闭包引用时期不超过变量的生命周期,没有野指针问题
  • 使用move时,变量所有权移动到闭包中
    • 环境中再无此变量
    • 由闭包负责释放此内存
    • 不会有内存安全问题
  • Rust为每个闭包生成一个新类型
    • 让闭包与代码关联
    • 不再需要函数指针

Rust闭包的类型

FnOnce

trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
  • call_once把闭包内部的数据移动到了函数内部
    • 闭包内部的数据被转移,闭包不完整,无法再使用
    • 闭包不转移内部数据,就不是FnOnce

      let c = move |g: String| (g, name.clone());
      

      这个闭包会clone内部的数据并返回,所以它不是FnOnce,可以执行多次

  • 把它变成FnOnce,则无法多次执行

     call_once("a".into(), c);
    
     fn call_once(name: String, c: impl FnOnce(String)->(String, String)) -> (String, String) {
         c(name)
     }
    

FnMut

trait FnMut<Args> : FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
  • FnMut也有call_once方法
  • 还有call_mut方法,传入&mut self,可以多次调用

闭包使用的场景

  • 作为函数参数
  • 作为函数返回值

      fn curry<T>(x: T) -> impl Fn(T) -> T
          where T: Mul<Output=T> + Copy {
              move |y| x * y
          }
    
  • 为闭包实现trait

      fn execute(cmd: &str, exec: impl Executor) {
          exec(cmd)
      }
    
      execute("cat /etc/passwd", |cmd: &str| {
          Ok(format!( "execute: env: {}, cmd: {}", env,cmd));
      })
    

创建一个KV server