闭包

概述

闭包只是定义了代码,存储代码,并不是执行。 闭包(Closure)也叫Lambda表达式或匿名函数。 不像普通函数,闭包可以对参数和返回类型进行推断,大多数时候都不需要写出来。 闭包是一种匿名类型,一旦声明,就会产生一个新的类型,但这个类型无法被其它地方使用。这个类型就像一个结构体,会包含所有捕获的变量。

创建闭包

格式

// fn 闭包
let fn = |参数列表| -> 返回类型 {代码段};
// FnMut 闭包
let mut fn = |参数列表| -> 返回类型 {代码段};
// FnOnce 闭包,move 可有可无,如果函数内发生所有权移交,也会触发
let fn = move |参数列表| -> 返回类型 {代码段};
// 无参闭包
let res = {
    // 代码段
    // 返回值
};
// 立即执行闭包
(|参数列表| -> 返回值 {代码段})(传入参数)

合法的定义

|| 42;
|x| x + 1;
|x:i32| x + 1;
|x:i32| -> i32 { x + 1 };

分类

闭包的类型 在Rust语言中,闭包是一种特殊的类型,被称为Fn、FnMut和FnOnce。这些类型用于区分闭包的捕获方式和参数类型。

Fn:表示闭包只是借用了自由变量,不会修改它们的值。这意味着,闭包可以在不拥有自由变量所有权的情况下访问它们。
FnMut:表示闭包拥有自由变量的可变引用,并且可能会修改它们的值。这意味着,闭包必须拥有自由变量的所有权,并且只能存在一个可变引用。
FnOnce:表示闭包拥有自由变量的所有权,并且只能被调用一次。这意味着,闭包必须拥有自由变量的所有权,并且只能在调用之后使用它们。

解释

Fn : 表示闭包以不可变引用的方式来捕获环境中的自由变量,同时也表示该闭包没有改变环境的能力,并且可以多次调用。对应 &self。
FnMut : 表示闭包以可变引用的方式来捕获环境中的自由变量,同时意味着该闭包有改变环境的能力,也可以多次调用。对应 &mut self。
FnOnce : 表示闭包通过转移所有权来捕获环境中的自由变量,同时意味着该闭包没有改变环境的能力,只能调用一次,因为该闭包会消耗自身。对应 self。如果你希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 move 关键字。这个技巧在将闭包传递给新线程以便将数据移动到新线程中时最为实用。

受限程度

Fn > FnMut > FnOnce
//顺序之所以是这样,是因为:
//&T只是获取了不可变的引用权
//&mut T则可以改变 变量的值
//T更厉害了,还能拿到了变量的所有权而非借用。

不同点

它们的调用方法分别采用&self、&mut self和self,这意味着Fn对其捕获具有不可变的访问权限,FnMut获得可变的访问权限,FnOnce可以获得所有权。 Fn和FnMut类型的闭包可以执行多次,而FnOnce类型的闭包只能执行一次。

move 关键字

如果没有实现Copy,就是move语义,会发生移动。
如果实现Copy(例如:#[derive(Copy)]),则是copy语义,会发生拷贝。
值得注意的是move与闭包的类型没有关系。
带move闭包,函数外和函数内的同名变量不是同一个变量。
不带move闭包,函数外和函数内的同名变量是同一个变量。
闭包函数执行完闭包后:带move闭包,使用闭包变量会产生【error[E0382]: borrow of moved value: p】错误。
闭包函数执行完闭包后:不带move闭包,使用闭包变量,正常执行。

特点

声明时使用 || 替代 () 将输入参数括起来。
函数体定界符({})对于单个表达式是可选的,其他情况必须加上。
有能力捕获外部环境的变量。

注意

参数列表可省略类型,但必须发生调用,否则编译报错。 返回值可为空,会自动推导。

闭包参数

Fn

Fn 可以通过不可变的引用捕获变量。

fn apply<F>(f: F) where F: Fn()
{
    f();
}
// or
fn apply<F>(f: impl Fn())
{
    f();
}


fn main() {
    let name = "jiangbo";
    let say = || println!("hello: {}", name);
    apply(say);
}

FnMut

可以通过可变引用捕获。

fn apply<F>(mut f: F) where F: FnMut()
{
    f();
}
// or
fn apply<F>(mut f: impl FnMut(u64)) 
{
    f();
}

fn main() {
    let mut name = "jiangbo".to_owned();
    let say = || {
        name.push_str("44");
        println!("hello: {:?}", name);
    };
    apply(say);
}

FnOnce

fn create_fnonce() -> impl FnOnce() {
    let text = "FnOnce".to_owned();
    move || println!("This is a: {}", text)
}

fn main() {
    let fn_once = create_fnonce();
    fn_once();
}

案例

简单案例

let closure = |a: i32| -> i32 {
    println!("a={}", a);
    a
};
println!("closure return {}", closure(10));
// 或
let closure = |a: i32| -> i32 println!("a={}", a);
println!("closure return {}", closure(10));
// 或
let F = |a: i32,b:i32| -> i32 {
    println!("a={},b={}", a,b);
    a+b
};
println!("a+b={}",F(1,2))

FnOnce闭包案例

fn main(){
    let a = Box::new(23);
    let call_me = || {
        let c = a;    
    }
    call_me();
    call_me();// 报错,这个示例中,闭包call_me中发生了所有权的移交(move语义),Box指针所指向的堆内存的所有权从a移交给了c,而随着c离开包后生命周期的结束,该堆内存也被释放掉。环境中的自由变量a在被闭包执行一次之后,就失效了。
}

Last updated