变量绑定:移动一个&mut还是借用引用?

15

以下代码会在 let c = a; 处因为编译错误 "use of moved value: a" 而失败:

fn main() {
    let a: &mut i32 = &mut 0;
    let b = a;
    let c = a;
}

a被移动到b中,不再可以分配给c。目前为止,一切都很好。

然而,如果我只注释b的类型,其余内容保持不变:

fn main() {
    let a: &mut i32 = &mut 0;
    let b: &mut i32 = a;
    let c = a;
}

代码再次在let c = a;处出现故障。

但这次错误消息完全不同:“无法移动a,因为它已被借用……*a的借用发生在此处:let b: &mut i32 = a;

所以,如果我只是注释b的类型,就没有将a移动到b中,而是“重新”借用了*a

我漏掉了什么吗?

干杯。

2个回答

10
因此,如果我只是注释b的类型:不会将a移动到b中,而是“重新”借用*a
你没有漏掉任何内容,在这种情况下,这两个操作在语义上非常相似(如果ab属于同一作用域,则等效)。
要么将引用a移动到b中,使a成为已移动值,并且不再可用。
要么在b中重新借用*a,只要b在作用域内,a就无法使用。
第二种情况比第一种情况不太确定,您可以通过将定义b的行放入子作用域来证明这一点。
因为a被移动,所以此示例无法编译。
fn main() {
    let a: &mut i32 = &mut 0;
    { let b = a; }
    let c = a;
}

但是这个会,因为一旦 b 超出作用域,a 就被解锁了:

fn main() {
    let a: &mut i32 = &mut 0;
    { let b = &mut *a; }
    let c = a;
}

现在,针对问题“为什么给b注释类型会改变行为?”,我的猜测是:
  • 没有类型注释时,操作是简单明了的移动,不需要检查。
  • 当有类型注释时,可能需要进行转换(将&mut _强制转换为&_,或将简单引用转换为对特定对象的引用)。因此,编译器选择重新借用该值,而不是移动它。
例如,以下代码是完全有效的:
fn main() {
    let a: &mut i32 = &mut 0;
    let b: &i32 = a;
}

在这种情况下,将a移动到b是没有意义的,因为它们是不同类型的。尽管如此,这段代码仍然可以编译: b只是重新借用了*a,并且只要b在作用域中,该值就不会通过a进行可变访问。


@dacker 嗯,记住这种行为只能通过 &mut 引用来实现,在这种情况下,重新借用只是一个遵守借用规则的副本。 - Levans
但是在我迄今阅读的所有文档中,这不是一个重要的观点吗:在这些情况下,&mut 总是被移动了吗?注意:对类型注释的 &mut 进行赋值时也会出现与上述相同的行为。 - dacker
让 b: &mut i32; b=a; 在哪里我还需要期望非移动(例如某些类型的副本),如果它适合编译器?这让我感到不自信。但如果事情就是这样,我会接受你的答案。 - dacker
唯一不会移动的非引用类型是实现了 Copy 的类型,& 引用是可以 Copy 的。移动或重新借用 &mut 引用几乎是相同的事情(我没有看到任何情况下移动比重新借用更好)。基本上就是这样了。但是,我将在编译器的 Github 上开一个问题,建议他们在文档中澄清这一点。 - Levans
我理解你的观点。我的观点只是文档说明与实际情况不同,但在这种情况下幸运的是没有造成任何伤害。我也想不出有什么例子依赖于移动值实际上被删除的情况。感谢你的努力! - dacker
显示剩余2条评论

3
为了补充 @Levans 关于特定问题“为什么注释类型会改变行为”的回答:
当您不写类型时,编译器执行简单的移动操作。当您写上类型时,let 语句就成为一个强制转换点,如 "强制转换点" 中所述:

可能的强制转换点包括:

  • 在指定显式类型的 let 语句中。
在这种情况下,编译器执行了一次 reborrow 强制转换,这是从 &mut&mut 的强制转换的特殊情况,如在 GitHub 上的此问题评论中 所解释的那样。
请注意,重新借用和重新借用强制转换通常都没有很好的文档说明。 Rust 参考手册有一个开放的问题,旨在改进这一点。

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