前言
以前我曾对这些概念有过误解。现在我看到很多新手仍然挣扎于此。我使用的术语可能并不标准,因此这里列出了一个表用以表达我的意思:
词 | 含义 |
---|---|
|
|
自有类型 | 一些非引用类型。例如 |
| 一些不可变的引用类型。例如 |
| 可变引用。例如 |
| 不可变引用。例如 |
误解
简而言之,变量的生命周期是编译器可以静态验证它指向的数据在其当前内存地址有效的时间。我现在用接下来的 ~6500 字来更详细地介绍人们通常会感到困惑的地方。
T
只包含自有类型
这个误解更多是关于泛型而不是生命周期的,但是在 Rust 中泛型和生命周期是紧密交叉的,抛开一方去谈另一方是不切实际的。
当我首次学习 Rsut 时,我能够理解 i32
, &i32
和 &mut i32
是不同的类型。我也能理解泛型变量 T
是一个包含了所有类型的集合。尽管两者单独分开我都能理解,但是放到一起我就很难理解了。在我的想法中,Rust 泛型是这样工作的:
类型变量 |
|
|
|
例子 |
|
|
|
T
包含了所有的自有类型、 &T
包含了所有的不可变引用、 &mut T
包含了所有的可变引用。 T
, &T
, &mut T
都是一个不相交的无穷集。想法很简单、清晰、直观,但是完全错误。下面是泛型在 Rust 中实际工作的方式:
类型变量 |
|
|
|
例子 |
|
|
|
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
和 &mut T
是不相交的:
trait Trait {}
impl<T> Trait for &T {} // ✅
impl<T> Trait for &mut T {} // ✅
关键概念
T
是&T
和&mut T
的超集。&T
和&mut T
是不相交集合。
若 T: 'static
则 T
必须在整个程序期间有效
误解
T: 'static
应当被读作 "T
with a'static
lifetime"&'static T
和T: 'static
是一种东西。若
T: 'static
则T
必须是不可变的。若
T: 'static
则T
只能在编译期创建。
大多户 Rust 新手第一次接触到 'static
生命周期接触的代码类似于:
fn main() {
let str_literal: &'static str = "str literal";
}
他们被告知 "字符串字面量"
是被硬编码编译到二进制中的、在运行时被加载到只读内存中、且在整个程序运行期间都有效,因而被标记为 'static
。而围绕 static
关键字定义的静态变量更加深了这一概念。
// Note: This example is purely for illustrative purposes.
// Never use `static mut`. It's a footgun. There are
// safe patterns for global mutable singletons in Rust but
// those are outside the scope of this article.
static BYTES: [u8; 3] = [1, 2, 3];
static mut MUT_BYTES: [u8; 3] = [1, 2, 3];
fn main() {
MUT_BYTES[0] = 99; // ❌ - 修改静态变量是不安全的。
unsafe {
MUT_BYTES[0] = 99;
assert_eq!(99, MUT_BYTES[0]);
}
}
关于静态变量:
只能在编译时创建。
应当是不可变的,修改它们是不安全的。
在整个程序运行期间都有效。
'static
生命周期很大概率是以静态变量的生命周期命名的,是吧?因此 'static
生命周期也遵循相同的规则,对吗?
事实上, a type with a 'static
lifetime 和 a type bounded by a 'static
lifetime 是不同的。后者可以在运行时动态分配,可以安全地且自由地修改,可以被 drop,可以存活任意长时间。
因此,辨别 &'static T
和 T: 'static
是一件非常重要的事情。
&'static T
是指向类型 T
的不可变引用,可以持有无限长时间,甚至跨越整个程序运行期间。这样的引用只有 T
本身是不可变的、且在创建引用后没有移动才有可能。 T
本身不需要在编译期间创建。它可以在运行地任意时刻创建,然后返回一个指向它的 'static
引用即可,付出的代价只是内存泄露。例如:
use rand;
// generate random 'static str refs at run-time
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
还包含了所有的自有类型,例如 String
, Vec
等。数据地 owner 被保证只要 owner 持有它,那么数据就不会失效,因而数据的 owner 可以安全地持有数据无限长时间。 T: 'static
应当被读作“ T
is bounded by a 'static
lifetime” 而不是 “ T
has a 'static
lifetime ”。下面的代码可以演示这些概念:
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() {
// all the strings are randomly generated
// and dynamically allocated at run-time
let string = rand::random::<u64>().to_string();
strings.push(string);
}
}
// strings are owned types so they're bounded by 'static
for mut string in strings {
// all the strings are mutable
string.push_str("a mutation");
// all the strings are droppable
drop_static(string); // ✅
}
// all the strings have been invalidated before the end of the program
println!("I am the end of the program");
}
关键概念
T: 'static
应当被读作 `T` 由'static
限定 。若
T: 'static
则T
要么是 with a'static
lifetime 的类型,要么是一个引用类型。由于
T: 'static
包含了类型为T
的自有类型,因而:可以在运行时动态分配。
不必在整个程序运行期间都有效。
可以被自有而安全地修改。
可以在运行时动态 drop。
可以有不同长度地生命周期。
&'a T
和 T: 'a
是一种东西
这个误解是上面那个误解的泛化版本。
&'a T