虚幻数据PhantomData
实际上,结构体本身也是可以有生命周期的,例如:
struct Tmp<'a>{index: &'a u32 }
上述声明中,虽然index为一个引用,但是这样声明后,相当于告诉编译器,Tmp
对象的生命周期会和index
保持一致。当然这并不会刻意的错误延长某些场景的生命周期,例如:
let test1 = 2;{let tmp = Tmp::new(&test1);}println!("{:?}", test1);
虽然我们生命周期中提到了tmp
和test1
的长度一致,但是这也不代表在上面的情况下,作为结构体的tmp被销毁的时test1也将无法使用。这是比较常见的场景,然而在某些特定的场合想,可能并非结构体中的某个成员变量,而是结构体本身会和某个对象关联,这种情况比较少,但是也不是完全不存在。例如在这种代码模型下:
#[derive(Debug)] pub struct Test1 {n1: u32 } impl Test1 {pub fn new() -> Test1 {Test1 {n1:1}}pub fn set_n(&mut self, n:u32) {self.n1 = n;}pub fn get_test2(&self) -> Test2{Test2 {n1:2}}}
此时Test2
对象由Test1
对象生成,这种模型常见于某些操作不安全数据的对象中,例如在会话对象中获取连接,抑或是从迭代器对象中获取数据,均可能出现这种写法。然而一般情况下,Rust是不允许直接声明一个结构体具有生命周期的,因为结构体的声明周期肯定需要关联到某个成员变量上,然而在上述模型中,显然是结构体生命周期与一些逻辑关联了。为了解决这种问题,Rust提出了一种叫做PhatomData(幽灵数据)
的数据结构,该结构不占据结构体中的任意一个空间,但是却可以充当生命周期使用。例如:
pub struct Test2<'a> {n1: u32,_marker: PhantomData<&'a Test1>, }
此时可以理解成,Test2
将会和Test1
上进行协变(covariant)。协变这个概念比较复杂,但是在这个例子中有一个更通俗的理解:无论Test2
结构体的生命周期有多长,它都将会收缩至和Test1
结构生命周期对齐。此时Test1中的声明需要改成
pub fn get_test2<'a>(&'a self) -> Test2<'a>{Test2 {n1:2, _marker:PhantomData}}
表明当前生命周期范围。如下的代码就是一个很好的例子
fn main() {let test3;println!("start test3");{let test1 = Test1::new();let test2 = test1.get_test2();test3 = test2;}println!("test3 is {:?}", test3); }
可以看到,test指向的是Test2
对象,并且生命周期比test1
要长。在未声明虚幻数据前,两个结构体之间没有关系,因此这段代码没有任何问题,然而在声明虚幻数据后,由于发生了协变,Test2
对象(也就是test3
)生命周期缩短至与test1一致,此时就会抛出错误:
error[E0597]: `test1` does not live long enough--> src/main.rs:213:21|
212 | let test1 = Test1::new();| ----- binding `test1` declared here
213 | let test2 = test1.get_test2();| ^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
214 | test3 = test2;
215 | }| - `test1` dropped here while still borrowed
...
219 | }| - borrow might be used here, when `test3` is dropped and runs the `Drop` code for type `Test2`|= note: values in a scope are dropped in the opposite order they are defined