免责声明:我有点匆忙,所以这篇文章可能有些冗长。请随意加以简化。
语言设计师最讨厌的“秘密技巧”基本上就是:Rust只能处理'static
生命周期(用于全局变量和其他整个程序生命周期的事物)和栈(即局部)变量的生命周期,无法表达或处理堆分配的生命周期。
这意味着几件事情。首先,所有与堆分配相关的库类型(例如Box<T>
、Rc<T>
、Arc<T>
)都拥有它们所指向的对象。因此,它们实际上不需要生命周期才能存在。
您需要生命周期的地方是当您访问智能指针的内容时。例如:
let mut x: Box<i32> = box 0;
*x = 42;
在第二行背后发生的事情是这样的:
{
let box_ref: &mut Box<i32> = &mut x;
let heap_ref: &mut i32 = box_ref.deref_mut();
*heap_ref = 42;
}
换句话说,因为 Box
并非神奇,我们必须告诉编译器如何将其转换为常规的借用指针。这就是Deref
和DerefMut
特性所用之处。这引出了一个问题: heap_ref
的生命周期是什么?
答案在DerefMut
的定义中(从内存中得出,因为我很匆忙):
trait DerefMut {
type Target;
fn deref_mut<'a>(&'a mut self) -> &'a mut Target;
}
就像我之前说的,Rust 绝对不能 谈论“堆生存期”。相反,它必须将堆分配的i32的生命周期与其手头上唯一的其他生命周期:即Box
的生命周期联系起来。
这意味着“复杂”的事物没有可表达的生命周期,因此必须拥有它们所管理的东西。当您将复杂智能指针/句柄转换为简单的借用指针时,那就是您必须引入生命周期的时刻,通常只使用句柄本身的生命周期。
实际上,我应该澄清一下:通过“句柄的生命周期”,我真正的意思是“句柄当前存储的变量的生命周期”:生命周期真正是为了存储而不是为了值。这通常是新手入门 Rust 时被绊倒的原因,因为他们无法弄清楚为什么不能做以下操作:
fn thingy<'a>() -> (Box<i32>, &'a i32) {
let x = box 1701;
(x, &x)
}
"但是……我知道这个盒子将继续存在,为什么编译器说它不会?!" 因为Rust无法推断堆生命周期,必须将&x
的生命周期绑定到变量x
,而不是它指向的堆分配。