前军教程网

中小站长与DIV+CSS网页布局开发技术人员的首选CSS学习平台

Rust编程思想(四)———— 灵动的闭包

Rust编程思想(四)———— 灵动的闭包

闭包已经不是函数,而是一个可以保存环境变量的函数,在现代的编程语言中已经是标配了,在Rust中,闭包需要注意的除了变量的捕获,还就是所有权问题了,变量一旦被闭包带走,那么这个变量将不能再被使用,主程序可能就有问题了。。

1. 闭包的定义(Fn闭包)

闭包使用 || 符号来定义,类似于匿名函数。闭包可以捕获环境中的变量,并在需要时进行调用

fn main() {
    let subtract= |a, b| a - b;
    let result = subtract (10, 3);
    println!("The result is: {}", result);
}

在上述示例中,我们定义了一个名为 subtract的闭包,它接受两个参数 a 和 b,并返回它们的差。我们通过 subtract (10, 3) 调用闭包,并将结果打印出来。可以看出,闭包和普通函数一样,可以接受参数并返回值。
创建闭包的语法如下:
let closure = |param1, param2, ...|
{
// 闭包函数体
};
使用 |param1, param2, ...| 来声明闭包的参数列表,参数之间使用逗号分隔。如果没有参数,则保留 ||。闭包主体使用 { ... } 包裹,可以是单个表达式或复合语句块,如果是单个表达式可以省略{ }

fn main() {
// 无主体的单行闭包
    let subtract= |a, b| a - b; 
    let mul = |x, y| { // 有主体的多行闭包
            let result = x * y;
            result
        };
    let result = subtract (10, 3); //7
    let another = mul(3, 5); // 15
}

捕获变量
闭包最大的特色在于它能够捕获定义它的环境中的变量。所谓捕获,是指闭包在运行时能够访问并使用这些环境变量,而不是简单地复制一份值。

fn main() {
    let  num = 5;
    let closure = |x| x+ num; // 捕获了 num 变量
    println!("The closure is: {}", closure(3));
    println!("The num is: {}", num);
}

需要注意的是,闭包只是借用了环境变量的所有权,而不是完全获取了所有权。因此,如果在环境中继续使用这个被捕获的变量,依然是合法的。

2. 闭包的类型

从闭包对所捕获变量的所有权权来分,可以分为在种,实现了以下 trait 之一(按灵活性递减顺序):

  • Fn:不可变借用捕获环境
  • FnMut:可变借用捕获环境
  • FnOnce:获取所有权捕获环境,只能调用一次
    默认的普通闭包是Fn,即不可变借用捕获环境,这样的闭包所捕捞的变量不可修改,如果要修改所捕捞变量,则需要使用FnMut闭包;如果要在闭包内取得所捕获变量的所有权,则需要使用 move 关键字,即实现了FnMut的闭包。

Fn闭包

闭包 Fn trait 描述了在闭包中只能使用不可变借用捕获环境变量。上面定义的闭包即为一个Fn闭包,闭包捕获的变量是只读的。

FnMut闭包

如果在闭包里需要修改所捕获的变量,则需要使用 FnMut trait,通过可变引用定义闭包。如:

let mut count = 0;
let mut increment = || {
    count += 1; // 可变借用 count
    println!("Count: {}", count);
};
increment(); // Count: 1
increment(); // Count: 2

FnOnce闭包(所有权与移动闭包)

上例中闭包只是借用了 num 的所有权,但有些情况下,我们可能需要将变量的所有权移动到闭包中。比如当捕获的是一个Box、Vec或其他拥有资源的类型时,闭包需要获取所有权,避免资源被提前释放。
使用 move 关键字,将 num 的所有权彻底转移到闭包中。

fn main() {
    let num = Box::new(5);
    let closure = move |x| *num + x;
    let result = closure(3); // 8
    println!("The result is: {}", result);
}

现在 num 的所有权已经转移到了闭包中,因此闭包可以安全地访问和使用 num。但是,在闭包外部继续使用 num 就是非法的了,因为所有权已经被移走。
总的来说,如果只是访问数据,闭包会自动借用环境变量;如果需要获取所有权,则需要使用 move 关键字显式转移所有权。

  • 如果闭包使用 move 后要移动的是可复制类型的值(如 i32),那么就会复制该值,这样即使创建了闭包,后面仍然可以使用捕获的变量
  • FnOnce闭包就是在闭包中将所捕获变量的所有权转移给了闭包,原来的变量存储空间被释放了,所以原来的变量将不能被再使用。

各种闭包对比



3. 闭包作为参数和返回值

闭包可以作为参数传递给其他函数,使代码变得更加灵活和抽象。

闭包其实在C/C++中就可以用函数指针来实现,也非常方便,而且函数指针也是强类型,可以作为参数传递,唯一的差异就是函数指针没有变量捕获,必须要通过参数来传入。

fn apply<F>(f: F, x: i32) -> i32
    where
        F: Fn(i32) -> i32,
{
    f(x)
}

fn create_closure() -> impl Fn() {
    let x = 5;
    move || println!("The value of x is: {}", x)
}
fn main() {
    //闭包作为参数
    let result2 = apply(|y| y * y, 5); // 25
    println!("The result2 is: {}", result2);
    //闭包作为返回值
    let closure = create_closure();
    closure();
}

上例中, apply 函数接受一个闭包 f 和一个整数 x 作为参数,并在内部调用闭包 f(x)。where 限制表示 F 实现了特定的特型 F: Fn(i32) -> i32,表示 F 是一个接受 i32 参数,返回 i32 结果的闭包类型。
调用 apply 时,我们传入了一个平方闭包 |y| y * y 和值 5,因此最终返回结果是 25。create_closure 函数,它返回一个闭包。通过这种方式,我们可以在不同的上下文中使用闭包,实现代码的复用和灵活性。

更多的使用闭包的目的还是在回调函数中,把闭包当作函数的参数传递,这时就理解为C/C++中的回调函数了,使用起来非常方便。但变量的捕获又是一把双刃剑,尤其使用FnMut闭包时会改变变量,所以需要谨慎使用。一般情况下尽量使用Fn闭包。

参考

Rust中的闭包

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言