美文网首页
Rust中的闭包:更快更安全

Rust中的闭包:更快更安全

作者: Loru_vest | 来源:发表于2020-06-19 18:06 被阅读0次

引子

Rust对函数式编程有着非常良好的支持,从闭包这一特性就能看出来,它不仅实现了经典的功能和语义,还派生出Fn, FnOnce, FnMut这几个trait帮助我们处理变量的所有权和引用的问题。

然而在这里,要重述一个事实,以防读者把在学习其他语言时产生的偏见带入Rust:闭包不等于匿名函数,它的正式定义

Operationally, a closure is a record storing a function together with an environment. ...

即闭包等于: “匿名”函数 + 从闭包外部(还是在当前作用域内)捕捉的变量,连同整个作用域一起,称之为环境(environment)。简单一点说,就是延伸了作用域的函数,其中包含了在函数主体中使用却未在函数签名中定义的变量。而函数是否匿名根本无关紧要。

Rust的闭包的完整语法格式为:|param: type| -> return type { func body }

其中||为闭包接受参数的地方,如果你调用了该闭包,得益于Rust的类型推导,参数类型和返回类型可以省略,否则不可省略。当函数主体只有一句时,可以省略那对花括号。

比如,一个简单的闭包:返回一个它接受的值:

let origin = |z| z;
let eight = origin(8);// eight: 8
let text = origin("text"); // compile error

值得注意的是,只要闭包的类型确定(不管是在类型推导后确定的还是最初就定义好的),就不能改变。

在深入讲解Rust的闭包之前,我们先看看在有垃圾回收(garbage collection)的语言中,闭包是什么样的。

# Python3: 一个计算平均值的函数
def cal_avg():
    useless_value = "I'm useless"
    cnt = 0                          #----------closure start----------#
    total = 0
    def average(new_value):
        nonlocal cnt, total
        cnt += 1
        total += new_value
        return total/cnt   #----------closure end----------#
    return average

def main():
    avg = cal_avg()
    print(avg(10)) // 10.0
    avg(5)         // 7.5
    print(avg(3))  // 6.0
if __name__ == "__main__":
    main()

在Python中,我们使用nonlocal来声明捕获的变量,否则就当作是局部变量使用。在这个例子中,average函数捕获了变量cnttotal,此外,在函数cal_avg内部,还定义了一个局部变量useless_value,这是为了说明普通的局部变量和被闭包引用的变量有什么不同,我们知道,一旦一个对象的引用计数为0时,它就不能再被获取(弱引用除外),就会被当作垃圾而回收掉以释放资源,很显然,在一个函数被调用完成后,除了返回值,其他所有局部变量都不可获取,但是闭包引用的值仍然没有被回收,这是因为,变量avg引用了函数值cal_avg()即内部的average函数,所以变量totalcnt一直都在被引用,这才没有被当作垃圾回收。

Rust没有垃圾回收,那是如何设计的呢?

借用

假设现在有一个描述城市的City结构体,包含这座城市的名字,人口数量,所在国家等等信息,那么它的定义应该如下:

struct City {
    name: String,
    country: String,
    population: f64,
  ...
}

假设我们现在有几个类型为City的值:new_york, seattle, london,组成的向量表:

const new_york = City {
    name: "New York".to_string(),
    country: "USA".to_string(),
    population: 851e4,
}

const seattle = City {
    name: "Seattle".to_string(),
    country: "USA".to_string(),
    population: 478e4,
}

const london = City {
    name: "London".to_string(),
    country: "UK".to_string(),
    population: 890e4,
}

fn main() {
    let mut city_list = vec![new_york, seattle, london];
}

我们想按照城市的人口数量的由多到少来为它进行排序,那么可以这么写:

fn sort_cities(cities: &mut Vec<City>, stat: Statistic) {
    cities.sort_by_key(|city| -city.get_statistic(stat)); // borrow a shared reference to stat
    println("{:?}", stat); // still fine
}

注意,闭包在这里使用了stat这个变量,而这个变量是在外部的函数中定义的。我们说闭包的这一行为是:捕捉变量。在这种情况下,它自动借用了stat的引用,理由很简单:因为闭包捕获了这个值,所以必然存在对它的引用。

移动

我们还可以把变量的所有权转移到闭包中,为此,使用关键字move

fn sort_cities(cities: &mut Vec<City>, stat: Statistic) {
    cities.sort_by_key(move |city| -city.get_statistic(stat)); // move the ownership of stat to the closure
    println!("{:?}", stat);// compile error! used moved value `stat`
  
    // If Statistic implements Copy, then stat is still avaiable
    println!("{:?}", stat);// It's fine
}

当然,如果闭包中捕获的变量实现了Copytrait,闭包会复制它而不是移动。

简而言之,Rust使用了生命周期而不是垃圾回收保证了安全性,然而,Rust的方法却快的多:甚至是快速垃圾回收都要比在存储在栈上的stat这种情况要慢一些,而Rust正是这样做的。

函数和闭包类型

函数和闭包都有各自的类型,举例来说:

fn insertion_sort(param: &mut Vec<i32>) {
    unimplemented!()
}


let num = vec![1, 2, 3];
let print_num_vector = |param| {
    for i in param {
        println!("{}", i);
    }
};

print_num_vector(&num); 

fn take_closure(closure: impl Fn(&Vec<i32>)) {
    unimplemented!();
}

上面这个函数的类型是:fn(&mut Vec<i32>),而我们描述闭包的类型时,是使用Fn, FnOnceFnMut这几个trait去描述它们的

但是,take_closure这个函数也可以接受一个函数作为参数,而fn()->()这种形式只适用于函数。

Fn, FnOnce, FnMut

  • 当一个闭包中只有对捕获变量的不可变引用时,我们说它实现了Fn这个trait。
  • 当一个闭包中发生了变量所有权的移动或者是某些值被消耗掉,drop, 我们说它实现的是FnOnce这个trait,即这个闭包只能使用一次。所有的函数和闭包都默认实现了这一trait。
  • 当一个闭包中出现了对变量的可变引用时,我们说它实现了FnMut这个trait。

它们三者之间的关系可以用集合论的方法来认识,FnOnce包含FnMut包含Fn

相关文章

  • Rust中的闭包:更快更安全

    引子 Rust对函数式编程有着非常良好的支持,从闭包这一特性就能看出来,它不仅实现了经典的功能和语义,还派生出Fn...

  • rust 闭包与同步

    rust 闭包与同步 rust 闭包 rust闭包中主要包括两个部分,闭包参数和闭包环境变量。闭包函数和函数参数使...

  • Rust 闭包初探

    Rust 中的闭包 Rust 中的闭包(closure)是一类特殊的函数。与普通函数相比,闭包是匿名的(当然你可以...

  • rust中的闭包

    Rust 的 闭包(closures)是可以保存进变量或作为参数传递给其他函数的匿名函数。可以在一个地方创建闭包,...

  • Rust 闭包学习 (Fn/FnMut/FnOnce)

    学习 Rust 闭包记录 闭包作为参数 闭包作为结构体属性 异步使用闭包 主要就是加 Send trait,没加 ...

  • 关于rust中的闭包(一)

    闭包 在计算机中,闭包 Closure, 又称词法闭包 Lexical Closure 或函数闭包 functio...

  • 2017-04-01 面向对象学习笔记

    闭包复习 闭包概念:封闭空间,包裹 闭包的作用:延长变量的声明周期保护内部的数据,代码更安全为我们提供一种间接访问...

  • Rust编程语言-13-函数式编程(闭包和迭代器)

    Rust语言的设计吸收了许多其它语言的优秀设计,比如函数式编程和闭包 闭包closure 能捕获环境参数的匿名函数...

  • rust中move、copy、clone、drop和闭包捕获

    rust中move、copy、clone、drop和闭包捕获 本文中的变量,指的是通过如下代码定义的常量a和变量b...

  • 2022-07-22

    1. rust 闭包 三种 Fn 的关系[https://course.rs/advance/functional...

网友评论

      本文标题:Rust中的闭包:更快更安全

      本文链接:https://www.haomeiwen.com/subject/eddbxktx.html