为什么创建一个可变引用指向一个已经被解引用的可变引用能够工作?

13

我知道在Rust中不能同时创建两个可变引用指向同一个对象。但我不完全理解为什么以下代码是有效的:

fn main() {
    let mut string = String::from("test");
    let mutable_reference: &mut String = &mut string;
    mutable_reference.push_str(" test");
    // as I understand it, this creates a new mutable reference (2nd?)
    test(&mut *mutable_reference);

    println!("{}", mutable_reference);
}

fn test(s: &mut String) {
    s.push_str(" test");
}
3个回答

14

规则

在任何时候,特定值只能有一个可用的可变引用。

这不是空间排除(同一块内存可以有多个引用),而是时间上的排除。

机制

为了实现这一点,&mut T 不是 Copy 类型;因此调用:

test(mutable_reference);

应将引用移至test中。

实际上这样做会使后续使用变得不可行且不太符合人体工程学,因此Rust编译器会自动插入重新借用(reborrowing),就像您自己所做的那样:

test(&mut *mutable_reference);

您可以强制移动,如果您想的话:
test({ let x = mutable_reference; x });

效果

重新借用本质上就是借用:

  • mutable_reference被借用的时间与未命名的临时可变引用存在的时间相同(或任何从中借用的内容),
  • 未命名的临时可变引用被移动到test中,
  • 在表达式结束时,未命名的临时可变引用被销毁,因此对mutable_reference的借用也随之结束。

1
我仍然很难理解这个reborrow。如果我们从生命周期的角度来看,mutable_reference的生命周期始于其定义,并且仅在主作用域结束时才超出范围。但是,在reborrow之间,它创建了一个匿名的可变借用,其生命周期是test函数的作用域。那么这两个生命周期之间是否存在交集,其中存在两个对字符串的可变引用? - raj
1
@raj:是的。你确实有深刻的问题!教学就是在说谎。你可能在学校学过牛顿物理学吧?教授相对论要容易得多,而且“足够”开始学习,因此它被教授,即使知道这是一个“谎言”。这里也是一样的:当我们说任何值的单个可变引用可能随时存在时,很容易记忆和记住,但它并不完全正确。事实上,任何值的单个可变引用可能随时是“可访问的”,借用检查器会拒绝重新借用的引用。 - Matthieu M.
谢谢Matthieu。巧合的是,我刚刚与一个反疫苗者聊天,我不得不使用牛顿的例子来说“被证明错误并不一定意味着无用”。无论如何,了解可变引用的有趣方面是很有趣的。如果您不介意,这里有类似的内容。https://dev59.com/kaj1oIgBc1ULPQZF6H8_ 。是否有任何好的资源可以更好地理解reborrow和生命周期? - raj
@raj:很抱歉,除了书籍和Stack Overflow的问答,我不知道其他的资源。 - Matthieu M.

7

在内存中是否存在多个可变指针引用同一位置?是的。

在代码中是否有多个指向同一位置且可用的可变指针?没有。

重新借用可变指针会锁定你重新借用的指针。


2
我分析了 test(mutable_reference)test(&mut *mutable_reference) 之间的 MIR 差异。后者只是引入了一个额外的间接级别:

code comparison: left is test(mutable_reference), right is test(&mut *mutable_reference)

当您在函数调用中使用额外的解引用时,它不会创建一个持有的可变引用;换句话说,在函数调用之外并没有任何实际区别。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接