我需要一些帮助来理解如何指定生命周期,以便让Rust理解我的意图(或者如果不可能,为什么不行)。
首先,这是我的基本情况,它可以正常工作。想象一个工厂类型Foo
,它创建Bar
,每个Bar
都借用了创建它的Foo
。借用的目的是当一个Bar
被丢弃时,它可以自动操作创建它的Foo
。显然,Foo
必须比Bar
存在更长的时间,这正是我的意图,也是我希望编译器验证的内容。
struct Foo {
s: String,
i: u64,
}
struct Bar<'a> {
parent: Option<&'a mut Foo>,
s: String,
i: u64,
}
impl Foo {
fn new(s: String) -> Foo {
Foo { s, i: 0 }
}
fn push<'a>(&'a mut self, s: String) -> Bar<'a> {
Bar { parent: Some(self), s, i: 0 }
}
fn print(&self) {
println!("{}:{}", self.s, self.i)
}
}
impl<'a> Bar<'a> {
fn print(&self) {
println!("{}:{}", self.s, self.i)
}
}
impl<'a> Drop for Bar<'a> {
fn drop(&mut self) {
if let Some(parent) = &mut self.parent {
parent.i += 1;
}
}
}
fn main() {
let mut x = Foo::new("x".to_string());
{
let y = x.push("y".to_string());
y.print(); // y:0
} // when y is dropped it increments x.i
x.print(); // x:1
}
现在我希望将这个模式递归,以便y
可以推入一个新的z
(其中y
必须像预期的那样比z
更长寿),以此类推。换句话说,我想要的是不再有单独的Foo
和Bar
,而是要有一个单一的类型Foo
,它的push
方法会创建新的指向它的Foo
。经过数小时的尝试,以下是我想到的最佳方案:
struct Foo<'a, 'b> where 'b: 'a {
parent: Option<&'a mut Foo<'b, 'b>>, // note 1
s: String,
i: u64,
}
impl<'a, 'b> Foo<'a, 'b> {
fn new(s: String) -> Foo<'a, 'b> { // note 2
Foo { parent: None, s, i: 0 }
}
fn push(&'b mut self, s: String) -> Foo<'a, 'b> { // note 2
Foo { parent: Some(self), s, i: 0 }
}
fn print(&self) {
println!("{}:{}", self.s, self.i);
}
}
impl<'a, 'b> Drop for Foo<'a, 'b> {
fn drop(&mut self) {
if let Some(parent) = &mut self.parent {
parent.i += 1;
}
}
}
fn main() {
let mut x = Foo::new("x".to_string());
{
let y = x.push("y".to_string()); // error 1
y.print(); // y:0
} // when y is dropped it increments x.i
x.print(); // x:1, error 2
}
注意事项和错误详情:
- 注意1:在
Foo<'b, 'b>
中两次使用相同的生命周期感觉不对,因为我并不想表达那个Foo
的两个生命周期需要匹配。然而,对于任何一个生命周期使用'a
也不对,而且我不能指定一个新的/不同的生命周期而不添加另一个生命周期参数到Foo
中,这反过来又迫使我在这里指定第三个生命周期,从而导致我遇到了同样的问题。看来这是递归的。只是为了好玩,我查找了一下Rust中是否有C++可变参数模板的等价物,但即使可能,它仍然感觉像是一个糟糕的解决方案。 - 注意2:这些行看起来不对,因为我在返回类型中使用了生命周期,在函数签名中没有出现,但我不知道该怎么办。
- 错误1:错误是“借用的值不够长”和“在这里放弃了x的引用,但仍然被借用”(其中“这里”是最后一行)。我的期望是
y
对x
的引用只在y
的生命周期内存在,但Rust显然认为生命周期不是这样工作的,所以似乎我没有告诉编译器如何约束(或不约束)生命周期。 - 错误2:这里的错误是“无法将
x
作为不可变借用,因为它也被作为可变借用”,这只是同样问题的另一个症状。我期望在调用x.print()
时,y
中的可变借用已经消失了,但编译器并没有这样认为。
当我寻找解决此问题的方法时,我发现很多都是关于创建循环引用的,其中父项有一个子项列表,每个子项都指向父项。我理解为什么这很困难/不可能,但我不认为这就是我在这里要做的事情。我的父/工厂类型不维护对其子项/小部件的引用;引用只沿着一个方向,从子项/小部件到父项/工厂。
我也发现自己阅读了与此相关的Rust规范的部分,但老实说,我已经超出了我的能力范围。至少有一个关于生命周期实际上如何工作的顿悟时刻我还没有,这使得我很难跟进所有规范级别的细节,更不用说它们与我的问题有关了。
我觉得我想让Rust做它擅长的事情--验证父项是否比子项更长寿--但我不确定如何向Rust解释当父项和子项具有相同类型时我要做什么。有人可以帮助我理解如何做到这一点或者为什么不能这样做吗?
Cell
,RwLock
等)相结合。我认为您可以只使用单个生命周期并依赖协变性? - Solomon UckoFoo::push
的self
参数类型为&'b mut Foo<'_, 'b>
。关于为什么这从来不是一个好主意的解释,请参见此答案。 - Jmb