[英]Lifetime in recursive struct with mutable reference
我正在尝试为树遍历定义一个类似于链表的递归结构。 一个节点有一些数据并可以访问它的父节点。 子节点应该可变地借用其父节点以确保独占访问,并在删除后释放它。 我可以使用不可变引用来定义这个结构,但是当我使父引用可变时就不行了。 在使父引用可变时,我对编译器错误感到困惑并且不理解它。
如何使用可变父引用定义这种递归结构的生命周期?
这是一个最小的例子。 这会编译但使用只读引用:
struct Node<'a> {
// Parent reference. `None` indicates a root node.
// I want this to be a mutable reference.
pub parent: Option<&'a Node<'a>>,
// This field just represents some data attached to this node.
pub value: u32,
}
// Creates a root node
// I use a static lifetime since there's no parent for the root so there are no constraints there
fn root_node(value: u32) -> Node<'static> {
Node {
parent: None,
value,
}
}
// Creates a child node
// The lifetimes indicates that the parent must outlive its child
fn child_node<'inner, 'outer: 'inner>(
parent: &'inner mut Node<'outer>,
value: u32,
) -> Node<'inner> {
Node {
parent: Some(parent),
value,
}
}
// An example function using the struct
fn main() {
let mut root = root_node(0);
let mut c1 = child_node(&mut root, 1);
let mut c2 = child_node(&mut c1, 2);
{
let mut c3 = child_node(&mut c2, 3);
let c4 = child_node(&mut c3, 4);
let mut cur = Some(&c4);
while let Some(n) = cur {
println!("{}", n.value);
cur = n.parent;
}
}
{
let c5 = child_node(&mut c2, 5);
let mut cur = Some(&c5);
while let Some(n) = cur {
println!("{}", n.value);
cur = n.parent;
}
}
println!("{}", c2.value);
}
我想要一个可变引用,所以我尝试替换Node
结构以使用可变引用:
struct Node<'a> {
// Parent reference. `None` indicates a root node.
// I want this to be a mutable reference.
pub parent: Option<&'a mut Node<'a>>,
// This field just represents some data attached to this node.
pub value: u32,
}
但后来我收到以下错误:
error[E0623]: lifetime mismatch
--> src/main.rs:25:22
|
21 | parent: &'inner mut Node<'outer>,
| ------------------------
| |
| these two types are declared with different lifetimes...
...
25 | parent: Some(parent),
| ^^^^^^ ...but data from `parent` flows into `parent` here
我不明白可变性和流入字段的数据之间的关系。 在不可变的情况下,我已经要求函数传递可变/独占引用。 我一直在尝试生命周期的各种组合(使用单一生命周期,逆转他们的关系等),但没有成功。
由于差异,不可能使用可变引用来实现这种递归结构。
| | 'a | T |
|-----------|-----------|-----------|
| &'a T | covariant | covariant |
| &'a mut T | covariant | invariant |
具体地, &'a mut T
是不变的关于T
。
这里的核心问题是节点只知道其父节点的生命周期,而不知道其所有祖先节点的生命周期。 即使在我的情况下,我只是对改变祖先的value
字段感兴趣, &mut Node
也可以访问修改链上任何祖先的parent
字段,在那里我们无法访问精确的生命周期。
这是一个示例,其中我的结构可能会导致可变父引用不健全。 如果T
在&'a mut T
是协变的,则将接受以下代码:
fn main() {
let mut root: Node<'static> = root_node(0);
// where 'a corresponds to `root`
let mut c1: Node<'a> = child_node(&mut root, 1);
{
let mut evil_root: Node<'static> = root_node(666);
{
// where 'b corresponds to `c1`
let mut c2: Node<'b> = child_node(&mut c1, 2);
// where 'c corresponds to `c2`
let mut c3: Node<'c> = child_node(&mut c2, 3);
// Here is the issue: `c3` knows that its ancestors live at least as long
// as `c2`. But it does not know how long exactly.
// With covariance, the lifetime of `evil_root` would be compatible since
// it outlives `c2`. And because `&mut T` enables to mutate any field
// we could do the following:
let c2_ref: &mut Node<'c> = c3.parent.unwrap();
let c1_ref: &mut Node<'c> = c2_ref.parent.unwrap();
*c1_ref.parent = Some(&mut evil_root);
}
}
// Trying to access the parent of `c1` now causes a read-after-free
println!("{}", c1.parent.unwrap().value);
}
不变性规则确保上面的代码被编译器拒绝并且没有不健全的地方。
因为&mut
允许修改任何字段,包括带有引用的字段,并且因为这种递归不会跟踪所有父生命周期,所以它是不健全的。 为了安全地实现这样的递归结构,Rust 需要一个允许改变value
的引用(因为它有一个静态生命周期,没有问题)而不是parent
。 在我上面发布的最小示例中,可以使用父级的不可变引用并将节点数据放在Cell
或RefCell
后面来RefCell
。 另一种可能的解决方案(但我没有深入研究)是将可变父引用放在Pin
后面,但取消引用它是unsafe
:我必须手动确保我永远不会更改parent
引用。
我的实际用例有点复杂,所以我将尝试通过将我的数据存储在由Vec
支持的堆栈中来重构它以消除对递归结构的需要。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.