Skip to the content.

Ownership and lifetime

这个 对于 ownership 的总结,实际上,技术就是 Rc 和 Cell

https://github.com/usagi/rust-memory-container-cs/blob/master/3840x2160/rust-memory-container-cs-3840x2160-dark-back.png

所以,Cell 的到底是怎么实现的呀?

看看这个教程了: https://github.com/tfpk/lifetimekata

borrow -> cell clone -> rc

检查工具

https://github.com/cordx56/rustowl

FnOnce:只能调用一次的闭包

参考 闭包

闭包本质是匿名 struct,捕获的变量成为其字段。根据如何捕获,Rust 把闭包分成三个 trait:

trait 捕获方式 调用次数
FnOnce 按值(所有权) 一次,调用即消耗闭包本身
FnMut 可变借用 多次,允许修改捕获的变量
Fn 不可变借用 多次

三者是继承关系:Fn: FnMut: FnOnce。即一个 Fn 闭包同时满足 FnMutFnOnce 约束。所以接受 F: FnOnce 的泛型函数可以接收任何闭包,这也是为什么很多 API(如 thread::spawnOption::unwrap_or_else)都用 FnOnce 约束——最宽松。

为什么 FnOnce 只能调用一次

闭包按值捕获后,捕获的变量就是闭包自己的字段。调用闭包时,FnOnce 的 trait 方法 call_once(self) 按值接收 self,闭包连同其捕获的所有权一起被消耗:

let s = String::from("hello");
let f = move || println!("{}", s); // 捕获 s 的所有权
f();
// f(); // error: use of moved value

典型场景

1. Option::take + unwrap:把一次性回调塞进 Option

FnOnce 调用即消耗自身,无法像 &Fn 那样反复借用调用。所以存 FnOnce 的经典模式是放进 Option,需要时用 Option::take 把所有权拿出来调用一次:

let mut opt = Some(|| println!("one-shot"));
let cb = opt.take().unwrap(); // 所有权拿出来
cb();
assert!(opt.is_none()); // 之后是 None,不可能再调用

2. Box<dyn FnOnce>:把闭包装箱擦除类型

dyn FnOnce 作为 trait object 时,调用同样消耗自身,只能调用一次:

let b: Box<dyn FnOnce()> = Box::new(|| println!("boxed"));
b();
// b(); // error: use of moved value

3. thread::spawn:move 闭包跨线程搬运所有权

std::thread::spawn 要求闭包满足 FnOnce + Send + 'staticmove 把捕获变量所有搬运进新线程,新线程只能执行一次这个闭包,正好对应 FnOnce:

let msg = String::from("hi");
let h = std::thread::spawn(move || println!("{}", msg));
h.join().unwrap();

与所有权的关系

rust 生命周期描述符

对于第二条,问题是,如果 self 的声明周期 和 其他参数不一致会怎样 ? 代码的逻辑的逻辑会被检查

struct Name<'a> {
    x: &'a str,
    y: &'a str,
}

impl<'a> Name<'a> {
    fn longest(&self, y: &str) -> &str {
        if self.x.len() > y.len() {
            self.x
        } else {
            self.y
        }
    }
}

为什么时候使用 lifetime

具体来说:

  1. 引用本身(最常见)
    • &’a str、&’a mut T 是最直接的场景,demo 里的 longest<’a> 就是。
  2. 包含引用的类型
    • 结构体字段存引用(RefHolder<’a>)、枚举存引用、Cow<’a, str>、&’a [T] 切片等,都需要把 lifetime 传进去,因为类型要”记住”内部引 用何时失效。
  3. trait 对象
    • Box<dyn Trait + ‘a>、&’a dyn Trait。这里的 ‘a 不是引用本身,而是说”这个 trait 对象内部可能借用数据,借用不能超过 ‘a”。
  4. ‘static 特例
    • &’static str 表示数据在整个程序运行期间都有效(字面量、常量),可以安全地存到全局、塞进 Box::leak 等。
  5. 省略规则(elision)掩盖了大部分情况
    • 大多数时候你不需要写 ‘a,编译器按规则自动补上(只有一个输入引用、&self 方法等)。写不出来的时候(比如 longest),恰恰说明借用 关系复杂到省略规则无法表达,必须显式标注。

所以本质是:lifetime 是编译期用来检查”引用不会比它指向的数据活得更久”的工具。凡是编译器无法自动推导出引用有效期的场合(多输入、类型 内部存引用、trait 对象),就需要显式写出 lifetime。demo 里的三个例子正好覆盖了这三类。

函数声明 lifetime elision 的原则, 为了解决函数返回值生命周期

  1. 没有返回值,可以省略
  2. &self &mut self 可以省略,因为按照 self
  3. 参数的 lifetime 都是一致的

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。