Rust闭包

41 次浏览
2024年07月21日创建

//trait、dyn、sized、:、::、&、*、mut、struct、Box、Ac、Debug、Add、Clone、Cfg、<>、fn、pub、vec、Result、Option、match、impl、for、->、=>、from、into、try_from、try_into、Copy、'a、'static、?、

闭包的自由变量的存储规则

除非你将闭包放在 Box、Vec 或其他容器中,否则 它们不会被分配到堆上。在大多数语言中,闭包会在堆中分配内存、进行动态派发以及进行垃 圾回收。因此,创建、调用和收集每一个闭包都会花费一点点额外的 CPU 时间。更糟的是,闭包往往难以 ,而内联是编译器用来消除 函数调用开销并实施大量其他优化的关键技术。

(a)在内存中,这个闭包看起 来像一个小型结构体,其中包含对其所用变量的引用。闭包 (b) 与闭包 (a) 完全相同,只不过它是一个 move 闭包,因此 会包含值而非引用。闭包 (b) 与闭包 (a) 完全相同,只不过它是一个 move 闭包,因此 会包含值而非引用。闭包 (c) 不会使用其环境中的任何变量。该结构体是空的,所以这个 闭包根本不会占用任何内存。

// 无参数的`Fn`特型和`FnOnce`特型的伪代码 
trait Fn() -> R {
  fn call(&self) -> R; 
}

trait FnOnce() -> R {
  fn call_once(self) -> R;
}

trait FnMut() -> R {
  fn call_mut(&mut self) -> R;
}

如果闭包只能安全地调用一次,那么 closure() 就会扩 展为 closure.call_once()。该方法会按值获取 self,因此这个 闭包就会被消耗掉

闭包的clone和copy

  • 一个不修改变量的非 move 闭包只持有共享引用,这些引用既能 Clone 也能 Copy,所以闭包也能 Clone 和 Copy。
  • 一个修改值的非move闭包在其内部表示中也可以有可变引用。可变引用既不能 Clone,也不能 Copy,使用它们的闭包同样如此。
  • 如果 move 闭包捕获的所有内容都能Copy,那它就能Copy。如果move 闭包捕获的所有内容都能Clone,那它就能 Clone。

即闭包也是可以copy或者克隆的,但是存在前提,如果这个闭包,不修改变量,入参数|x| x+n; 比如这样的,就可以clone和copy。如果是改变外面的值:let mut add_to_x = |n| { x += n; x };,则不能copy。如果是move的话,则move的自由变量能copy,则能copy。

let mut greeting = String::from("Hello, ");
let greet = move |name| {
    greeting.push_str(name);
    println!("{}", greeting);
};

因为rust的闭包存在不同类型,包括无限调用次数Fn,调用一次FnOnce、可变多次FnMut,所以在定义函数签名的时候需要注意这些点。

什么情况下会使用Fn、FnOnce和FnMut?

在 Rust 中,闭包可以实现三种不同的特性:`Fn`、`FnMut` 和 `FnOnce`。这些特性表示闭包如何使用捕获的变量:

1. **`Fn`**:表示闭包通过不可变借用捕获变量,可以多次调用。

2. **`FnMut`**:表示闭包通过可变借用捕获变量,可以多次调用。

3. **`FnOnce`**:表示闭包通过值捕获变量,只能调用一次。

要使一个闭包实现 `Fn` 特性,闭包必须只通过不可变借用捕获变量,且不对捕获的变量进行任何修改或消耗。也就是说,闭包中的所有操作都不会改变捕获的变量的状态。

### 示例

以下是一些实现 `Fn` 特性的闭包示例:

例子 1:简单的不可变借用

fn main() {
    let x = 5;
    let add_x = |y| x + y; // 闭包通过不可变借用捕获 x

    println!("{}", add_x(2)); // 输出 7
    println!("{}", add_x(3)); // 输出 8
}

在这个例子中,闭包 `add_x` 通过不可变借用捕获了 `x`,并且在闭包内部没有修改 `x` 的值,因此这个闭包实现了 `Fn` 特性,可以多次调用。

例子 2:捕获不可变引用

fn main() {
    let s = String::from("Hello");

    let print_s = || println!("{}", s); // 闭包通过不可变借用捕获 s

    print_s(); // 输出 "Hello"
    print_s(); // 输出 "Hello"
}

在这个例子中,闭包 `print_s` 通过不可变借用捕获了 `s`,并且在闭包内部只是打印 `s`,没有修改 `s` 的值,因此这个闭包也实现了 `Fn` 特性,可以多次调用。

如何确定闭包的特性

Rust 编译器会根据闭包如何使用捕获的变量自动推断闭包的特性。如果你希望明确指定闭包的特性,可以使用特性约束。例如:

fn call_fn<F: Fn()>(f: F) {
    f();
}

fn main() {
    let s = String::from("Hello");

    let closure = || println!("{}", s); // 闭包通过不可变借用捕获 s

    call_fn(closure); // 第一次调用
    call_fn(closure); // 第二次调用
}

在这个例子中,我们定义了一个 `call_fn` 函数,它接受一个实现了 `Fn` 特性的闭包作为参数。由于闭包 `closure` 实现了 `Fn` 特性,因此可以多次调用。

总结

- 要使闭包实现 `Fn` 特性,闭包必须通过不可变借用捕获变量,并且不对捕获的变量进行任何修改或消耗。

- Rust 编译器会根据闭包的使用情况自动推断闭包的特性。

- 你也可以通过特性约束明确指定闭包的特性。

希望这些信息对你理解 Rust 中闭包的特性有所帮助!如果你有更多问题,请随时提问。