【翻译】Rust生命周期常见误区

本文探讨了关于Rust编程语言中生命周期的一些常见误解,包括生命周期与泛型的关系、生命周期与所有权类型的区别、静态变量与生命周期的混淆、生命周期省略规则的应用、编译器错误信息的理解以及闭包和引用转换的注意事项。通过澄清这些误解,有助于提高Rust程序员对代码内存安全的理解和实践。
摘要由CSDN通过智能技术生成

5月19日, 2020 · 阅读大概需要37分钟 · #rust · #生命周期

目录

  • 介绍

  • 误解列表

    • 1) T 只包含所有权类型

    • 2) 如果 T: 'static 那么 T 必须在整个程序运行中都是有效的

    • 3) &'a T 和 T: 'a 是相同的

    • 4) 我的代码没用到泛型,也不含生命周期

    • 5) 如果编译能通过,那么我的生命周期标注就是正确的

    • 6) 装箱的trait对象没有生命周期

    • 7) 编译器报错信息会告诉我怎么修改我的代码

    • 8) 生命周期可以在运行时变长缩短

    • 9) 将可变引用降级为共享引用是安全的

    • 10) 闭包遵循和函数相同的生命周期省略规则

    • 11) 'static 引用总能强制转换为 'a 引用

  • 总结

  • 讨论

  • 关注

介绍

我曾经有过的所有这些对生命周期的误解,现在有很多初学者也深陷于此。我用到的术语可能不是标准的,所以下面列了一个表格来解释它们的用意。

短语 意为
T 1) 包含了所有可能类型的集合 
2) 这个集合中的类型
所有权类型 不含引用的类型, 例如 i32StringVec, 等
1) 借用类型 
2) 引用类型
不考虑可变性的引用类型, 例如 &i32&mut i32, 等
1) 可变引用 
2) 独占引用
独占的可变引用, 即 &mut T
1) 不可变引用 or
2) 共享引用
共享的不可变引用, 即 &T

误解列表

简而言之:变量的生命周期指的是这个变量所指的数据可以被编译器静态验证的、在当前内存地址有效期的长度。我现在会用大约~8000字来详细地解释一下那些容易误解的地方。

1) T 只包含所有权类型

这个误解比起说生命周期,它和泛型更相关,但在Rust中泛型和生命周期是紧密联系在一起的,不可只谈其一。

当我刚开始学习Rust的时候,我理解i32&i32,和&mut i32是不同的类型,也明白泛型变量T代表着所有可能类型的集合。但尽管这二者分开都懂,当它们结合在一起的时候我却陷入困惑。在我这个Rust初学者的眼中,泛型是这样的运作的:





类型变量 T &T &mut T
例子 i32 &i32 &mut i32

T 包含一切所有权类型; &T 包含一切不可变借用类型; &mut T 包含一切可变借用类型。 T, &T, 和 &mut T 是不相交的有限集。简洁明了,符合直觉,但却完全错误。下面这才是泛型真正的运作方式:





类型变量 T &T &mut T
例子 i32&i32&mut i32&&i32&mut &mut i32, ... &i32&&i32&&mut i32, ... &mut i32&mut &mut i32&mut &i32, ...

T&T, 和 &mut T 都是无限集, 因为你可以无限借用一个类型。 T 是 &T 和 &mut T的超集. &T 和 &mut T 是不相交的集合。让我们用几个例子来检验一下这些概念:

trait Trait {}

impl<T> Trait for T {}

impl<T> Trait for &T {} // 编译错误

impl<T> Trait for &mut T {} // 编译错误

上面的代码并不能如愿编译:

error[E0119]: conflicting implementations of trait `Trait` for type `&_`:
 --> src/lib.rs:5:1
  |
3 | impl<T> Trait for T {}
  | ------------------- first implementation here
4 |
5 | impl<T> Trait for &T {}
  | ^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&_`

error[E0119]: conflicting implementations of trait `Trait` for type `&mut _`:
 --> src/lib.rs:7:1
  |
3 | impl<T> Trait for T {}
  | ------------------- first implementation here
...
7 | impl<T> Trait for &mut T {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&mut _`

编译器不允许我们为&T&mut T实现Trait,因为这样会与为T实现的Trait冲突, T本身已经包含了所有&T&mut T。下面的代码能够如愿编译,因为&T&mut T是不相交的:

trait Trait {}

impl<T> Trait for &T {} // 编译通过

impl<T> Trait for &mut T {} // 编译通过

要点

  • T 是 &T 和 &mut T的超集

  • &T 和 &mut T 是不相交的集合

2) 如果 T: 'static 那么 T 必须在整个程序运行中都是有效的

误解推论

  • T: 'static 应该被看作 T 拥有 'static 生命周期 "

  • &'static T 和 T: 'static 没有区别

  • 如果 T: 'static 那么 T 必须为不可变的

  • 如果 T: 'static 那么 T 只能在编译期创建

大部分Rust初学者是从类似下面这个代码示例中接触到 'static 生命周期的:

fn main() {
    let str_literal: &'static str = "str literal";
}

他们被告知 "str literal" 是硬编码在编译出来的二进制文件中的, 并会在运行时被加载到只读内存,所以必须是不可变的且在整个程序的运行中都是有效的, 这就是它成为 'static 的原因。而这些观念又进一步被用 static 关键字来定义静态变量的规则所加强。

static BYTES: [u8; 3] = [1, 2, 3];
static mut MUT_BYTES: [u8; 3] = [1, 2, 3];

fn main() {
   MUT_BYTES[0] = 99; // 编译错误,修改静态变量是unsafe的

    unsafe {
        MUT_BYTES[0] = 99;
        assert_eq!(99, MUT_BYTES[0]);
    }
}

认为静态变量

  • 只可以在编译期创建

  • 必须是不可变的,修改它们是unsafe的

  • 在整个程序的运行过程中都是有效的

'static 生命周期大概是以静态变量的默认生命周期命名的,对吧?那么有理由认为'static生命周期也应该遵守相同的规则,不是吗?

是的,但拥有'static生命周期的类型与'static约束的类型是不同的。后者能在运行时动态分配,可以安全地、自由地修改,可以被drop, 还可以有任意长度的生命周期。

在这个点,很重要的是要区分 &'static T 和 T: 'static

&'static T是对某个T的不可变引用,这个引用可以被无限期地持有直到程序结束。这只可能发生在T本身不可变且不会在引用被创建后移动的情况下。 T并不需要在编译期就被创建,因为我们可以在运行时动态生成随机数据, 然后以内存泄漏为代价返回'static引用,例如:

use rand;

// 在运行时生成随机&'static str
fn rand_str_generator() -> &'static str {
    let rand_string = rand::random::<u64>().to_string();
    Box::leak(rand_string.into_boxed_str())
}

T: 'static 是指T可以被无限期安全地持有直到程序结束。 T: 'static包括所有&'static T,此外还包括所有的所有权类型,比如StringVec等。数据的所有者能够保证数据只要还被持有就不会失效,因此所有者可以无限期安全地持有该数据直到程序结束。 T: 'static应该被看作“T'static生命周期约束”而非“T有着'static生命周期”。这段代码能帮我们阐释这些概念:

use rand;

fn drop_static<T: 'static>(t: T) {
    std::mem::drop(t);
}

fn main() {
    let mut strings: Vec<String> = Vec::new();
    for _ in 0..10 {
        if rand::random() {
            // 所有字符串都是随机生成的
         
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值