在我期望可变借用结束之后,我遇到了关于同时使用可变借用和不可变借用的令人困惑的错误。我对类似问题做了很多研究(1,2,3,4,5),这让我相信我的问题与词汇生命周期有关(虽然打开 NLL 功能并在夜间编译不会改变结果),但我只是不知道是什么;我的情况似乎不符合其他问题的任何场景。
pub enum Chain<'a> {
Root {
value: String,
},
Child {
parent: &'a mut Chain<'a>,
},
}
impl Chain<'_> {
pub fn get(&self) -> &String {
match self {
Chain::Root { ref value } => value,
Chain::Child { ref parent } => parent.get(),
}
}
pub fn get_mut(&mut self) -> &mut String {
match self {
Chain::Root { ref mut value } => value,
Chain::Child { ref mut parent } => parent.get_mut(),
}
}
}
#[test]
fn test() {
let mut root = Chain::Root { value: "foo".to_string() };
{
let mut child = Chain::Child { parent: &mut root };
*child.get_mut() = "bar".to_string();
} // I expect child's borrow to go out of scope here
assert_eq!("bar".to_string(), *root.get());
}
错误是:
error[E0502]: cannot borrow `root` as immutable because it is also borrowed as mutable
--> example.rs:36:36
|
31 | let mut child = Chain::Child { parent: &mut root };
| --------- mutable borrow occurs here
...
36 | assert_eq!("bar".to_string(), *root.get());
| ^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
我理解为什么不可变借用发生在那里,但我不明白可变借用是如何在那里使用的。两者如何在同一个地方使用?我希望有人可以解释发生了什么以及我如何避免它。
简而言之,
&'a mut Chain<'a>
具有极大的局限性和普遍性。
对于不可变引用
&T<'a>
,编译器可以在必要时缩短'a
的生命周期以匹配其他生命周期或作为NLL的一部分(这不是always的情况,这取决于T
是什么) )。但是,它不能对可变引用执行此操作&mut T<'a>
,否则您可以为其分配一个生命周期较短的值。
因此,当编译器尝试协调引用和参数链接时的生命周期
&'a mut T<'a>
时,引用的生命周期在概念上会扩展以匹配参数的生命周期。这本质上意味着您创建了一个永远不会被释放的可变借用。
将这些知识应用于您的问题:只有当嵌套值在其生命周期内协变时,创建基于引用的层次结构才可能真正实现。其中不包括:
请参阅 playground 上的这些变体,看看它们如何不按预期工作。
另请参阅:
为了好玩,我将提供一个例子,其中 Rust 标准库“故意”做这种事情。 std::thread::scope
的签名如下:
pub fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T
提供给用户定义函数的
有意将其生命周期捆绑在一起,以确保它仅以预期的方式使用。情况并非总是如此,因为结构对其泛型类型可能是协变或逆变的,但
Scope
被定义为不变的。此外,唯一可以调用它的函数是 .spawn()
,它也有意将 &'scope self
作为自参数,确保引用的生命周期不会比 scope
给出的生命周期短。在内部,标准库包含此文档(源不可变'scope
的不变性,以确保
不会收缩,这是稳健性所必需的。如果没有不变性,这会编译得很好,但不健全:'scope
即使引用的生命周期相对于自身而言是不变的,这仍然避免了上述许多问题,因为它使用了
std::thread::scope(|s| { s.spawn(|| { let a = String::from("abcd"); s.spawn(|| println!("{a:?}")); // might run after `a` is dropped }); });
引用和内部可变性。如果 .spawn()
的参数需要
&'scope mut self
,那么这将不起作用,并且在尝试生成多个线程时会遇到与上述相同的问题。drop
不会改变错误。问题在于
&'a mut Chain<'a>
- 迫使 root
在其整个生命周期中被借用,在借用被放弃后使其变得毫无用处。根据下面的评论,用生命周期来做到这一点基本上是不可能的。我建议用盒子代替。将结构更改为pub enum Chain{
Root {
value: String,
},
Child {
parent: Box<Chain>,
},
}
并根据需要调整其他方法。或者,如果您希望原件保持可用而不消耗自身,请使用
Rc<RefCell<Chain>>
。