提问人:rmeador 提问时间:2/18/2021 最后编辑:JamesThomasMoonrmeador 更新时间:12/27/2022 访问量:1349
为什么这种可变借用超出了它的范围?
Why does this mutable borrow live beyond its scope?
问:
在我期望可变借用结束之后,我遇到了一个令人困惑的错误,即同时使用可变和不可变借用。我对类似的问题(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>
对于不可变引用,编译器可以在必要时缩短生存期以匹配其他生存期或作为NLL的一部分(情况并非总是如此,这取决于是什么)。但是,它不能对可变引用执行此操作,否则您可以为其分配一个生存期较短的值。&T<'a>
'a
T
&mut T<'a>
因此,当编译器尝试协调引用和参数链接时的生存期时,引用的生存期在概念上会扩展以匹配参数的生存期。这实质上意味着你已经创建了一个永远不会释放的可变借用。&'a mut T<'a>
将这些知识应用于您的问题:只有当嵌套值在其生命周期内协变时,才有可能创建基于引用的层次结构。其中不包括:
- 可变引用
- trait 对象
- 具有内部可变性的结构
请参阅 Playground 上的这些变体,看看它们如何无法按预期工作。
另请参阅:
为了好玩,我将包括一个 Rust 标准库故意做这种事情的案例。std::thread::scope
的签名如下所示:
pub fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T
有意将提供给用户定义函数的作用域
的生存期捆绑在一起,以确保它仅以预期的方式使用。情况并非总是如此,因为结构体可能在其泛型类型上是协变的或逆变的,但被定义为不变的。此外,唯一可以调用的函数是有意将 self 也作为自参数的函数,以确保引用的生存期不会短于 给出的寿命。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 }); });
即使引用的生存期相对于自身是不变的,这仍然避免了上述许多问题,因为它使用不可变引用和内部可变性。如果参数为 required ,则这将不起作用,并且在尝试生成多个线程时遇到上述相同的问题。.spawn()
&'scope mut self
评论
问题不在于词法生存期,添加显式不会更改错误。问题在于 - 强制在其整个生命周期内被借用,使其在借用被放弃后变得毫无用处。根据下面的评论,在一生中这样做基本上是不可能的。我建议改用一个盒子。将结构更改为drop
&'a mut Chain<'a>
root
pub enum Chain{
Root {
value: String,
},
Child {
parent: Box<Chain>,
},
}
并根据需要调整其他方法。或者,如果您希望原始文件在不消耗自身的情况下保持可用,请使用。Rc<RefCell<Chain>>
评论
Box
Rc<RefCell<Chain>>
Rc
RefCell
评论