rust的函数指针与闭包

1,898 阅读3分钟

函数指针

rust中定义函数签名是显式制定的,保证了编译器的检查

  1. 函数项类型可以通过显式指定函数类型转换为一个函数指针类型
  2. 在写代码的时候,尽可能地去使用函数项类型,不到万不得已不要使用函数指针类型,这样有助于享受零大小类型的优化。
let example  = demo; 
let c: fn(&str)->bool= example; // fn pointer type
assert_eq!(0,std::mem::size_of_val(&example));
assert_eq!(8,std::mem::size_of_val(&c));

闭包

闭包可以捕获环境中的自由变量,这一点弥补了函数的缺陷

fn fiber(x: i32)-> fn(i32)-> i32{
    fn add(y: i32)-> i32{
        x+y // error[E0434]: can't capture dynamic environment in a fn item
    }
    add
}
fn main() {
    let f1 = fiber(1);
    assert_eq!(2,f1(1));// help: use the `|| { ... }` closure form instead
}

闭包的使用方法如下:

fn fiber(x: i32) -> impl FnMut(i32)-> i32 {
    move |y| x+y
}
fn main() {
    assert_eq!(2,fiber(1)(1));
}

rust中闭包的实现原理:

  1. 未捕捉环境变量 ----->所有权机制
  2. 捕捉变量并修改 ----->可变借用(&mut T)
  3. 捕捉变量但不修改 ----->不可变借用(&T )

rust所有权语义的核心理念可以归结为“可变不共享,共享不可变”的规则

​ ---《深入浅出rust》

即:

  1. 如果没有捕获上下文的变量,则实现FnOncetrait
  2. 如果捕获了变量,但未对变量进行修改的实现FnMut
  3. 如果捕获了变量并且进行修改,则实现Fntrait

特殊情况:

  1. 编译器会将FnOnce当作fn(T)函数指针看待。
  2. Fn->FnMut->FnOnce三者的关系是依次继承。
#![feature(unboxed_closures, fn_traits)]
struct Closure{
    env_car: i32
}
impl FnOnce<()> for Closure{
    type Output = ();
    #[warn(unused_variables)]
    extern "rust-call" fn call_once(self, args: () ) -> (){
        println!("FnOnce{}",self.env_car)
    }
}
impl FnMut<()> for Closure{
    extern "rust-call" fn call_mut(&mut self, args: () ) ->(){
        println!("可变引用FnMut{}",self.env_car)
    }
}
impl Fn<()> for Closure{
    extern "rust-call" fn call(&self, args: () ) -> (){
        println!("不可变引用Fn{}",self.env_car)
    }
}

fn main() {
    let demo = 1;
    let c = Closure {
        env_car: demo
    };
    c.call(())
}

如果要在函数内返回一个闭包,需要用到move关键字,这样会捕获函数内部变量,并转移该变量的所有权。如果是Sring类型这种没有实现Copy语义的类型,将不能捕获,move也会失效。这两种情况的闭包分别称为逃逸闭包和非逃逸闭包。

根据所有权的语义,不可变引用是可以同时存在多个的,但是在闭包里有一种情况只能存在唯一的不可变引用:

let mut a = [1,2,3];
let x = &mut a;
{
    let mut c = || {(*x)[0] = 0;};
    let  y = &x; // 报错 second borrow occurs here
    c();
}
let z = &x; //ok

在上面的代码里先定义一个固定长度的数组,接着取a的可变借用x,然后在一个表达式内部定义c是一个闭包,这个闭包的作用是将a的第一个元素修改为0,接着再取x的解引用,这里编译器报错显示cannot borrow x because previous closure reuqires unique access,意思是第一次的索引已经存在了x的不可变引用(隐式),对于x的第二次借用编译器便不允许对于闭包捕获变量的第二次不可变借用。

闭包的trait

trait作为rust的核心,闭包又实现了哪些trait

  1. 闭包都默认实现了Sizedtrait
  2. 如果所有捕获的变量都实现了Copytrait,并且以可变引用的方式修改它,这样闭包就不会有Copytrait。
  3. 如果捕获的变量带有Move语义,且闭包内有修改或者消耗该变量,则闭包不会有Copytrait。

以上的三点其实也都对应了rust的所有权语义,即不能同时存在多个可变引用。