在Rust中如何以可变和不可变的方式借用?

9

我已经阅读了这些文档:https://doc.rust-lang.org/rust-by-example/scope/borrow/mut.html

我也读过这个问题:(Cannot borrow immutable borrowed content as mutable)


这些文档帮助我理解如何声明可变借用(我想):

let mut (part1, part2) = someTuple;

但我没有找到有关将借用作为不可变量的明确说明。这是我的猜测:

let (part1, part2) = someTuple;

我知道这是一个非常基础的问题,但是通过谷歌搜索得到的解释太深奥了,我仍然在努力理解最简单的上下文。

在Rust中如何借用为可变变量(mut)和不可变变量(immutable)?

2个回答

9
let x = 0;
let immutable_borrow = &x; //borrow as immutable

//to borrow as mutable the variable needs to be declared as mutable
let mut y = 1;
let mutable_borrow = &mut y; //borrow as mutable
注意1:在同一作用域内,变量既可以作为不可变的变量被借用,也可以作为可变的变量被借用,即不能同时进行以下操作:
let mut x = 0;
let immutable_borrow = &x;
let mutable_borrow = &mut x;

为什么?

因为如果你同时拥有同一变量的可变和不可变引用,那么通过可变引用更改该变量的数据可能会引起许多问题。


注2:您可以无限次地对变量进行不可变借用,但只能对变量进行可变借用一次。

//You can do this
let x = 0;
let x1 = &x;
let x2 = &x;
//...

//But you can't do this
let mut y = 0;
let y1 = &mut y;
let y2 = &mut y; //won't compile if you use y1 after you declare y2

为什么?

如上所述,一个可变引用可能会在其他不知情的可变引用指向的数据发生更改,这可能会导致许多问题。但是拥有多个不可变引用是可以的,因为数据不会意外更改。


值得注意的是,由于词法作用域,您最后的示例确实可以编译。因为 y2 是在最后一次使用 y1 之后声明的,所以第二个可变借用是允许的,因为作用域从未重叠。只有当您尝试在对 y2 进行可变借用之后使用 y1 时,此示例才会被借用检查器拒绝。您的第二个示例也是如此--实际上,您列出的所有“非法”借用在 Rust 中都是完全允许的。 - Brian61354270
你的意思是 NLL(非词法生命周期),而不是“词汇作用域”吗? - leshow

2
"Ejdrien answers"展示了可变和不可变借用之间的区别,但它并没有解决您问题中的一个小标题,即您正在执行作为模式匹配的一部分的借用。

当您编写:

let (mut part1, mut part2) = someTuple;

您正在通过移动它们,将someTuple的内容绑定到可变变量part1part2。除非someTuple的内容是Copyable的,否则变量part1part2将成为各自值的独占所有者。如果您尝试以后写入访问someTuple,例如:
println!("{}", someTuple.0);

如果您从借用检查器中收到类似的编译错误:

error[E0382]: borrow of moved value: `someTuple.0`
 --> main.rs:6:20
  |
4 |     let (mut part1, mut part2) = someTuple;
  |          --------- value moved here
5 | 
6 |     println!("{}", someTuple.0);
  |                    ^^^^^^^^^^^ value borrowed here after move

在这种特定的情况下,有两种方法可以通知编译器我们只想借用 someTuple 的内容。第一种方法是 Ejdrien 描述的技术,即显式地借用元组,然后对结果引用执行模式匹配:

// Produce two mutable references
let (part1, part2) = &mut someTuple;

// Produce two immutable references
let (part1, part2) = &someTuple;

这种方法的问题在于我们被迫以同样的方式借用全部内容。如果我们只需要对someTuple.0进行可变引用,并且想要将someTuple.1作为副本或者不可变引用检索出来,怎么办?对于这个元组示例来说,这可能不太重要,但是对于更复杂的模式匹配情况,拥有这种类型的控制就更加重要了。
这带我们来到第二个解决方案:绑定引用。我们可以写成如下形式,而不是上面所述的方式。
// Produce two mutable references
let (ref mut part1, ref mut part2) = someTuple;

// Produce two immutable references
let (ref part1, ref part2) = someTuple;

在这里,我们明确说明了如何绑定模式匹配中的每个变量。关键在于我们可以自由地混合可变和不可变的借用,因此以下内容也完全有效:

// Produce immutable reference and one mutable reference
let (ref part1, ref mut part2) = someTuple;

println!("{}", &someTuple.0); // Make a second immutable reference someTuple.0
*part2 = ... // Mutate someTuple.1
println!("{}", part1); // Continue using the immutable reference

如果我们将上述内容替换为右侧的显式可变借用,由于同时存在可变和不可变引用,我们将再次从借用检查器中收到错误提示:
let (part1, part2) = &mut someTuple;

println!("{}", &someTuple.0);
*part2 = ... // Mutate someTuple.1
println!("{}", part1);

生产

error[E0502]: cannot borrow `someTuple.0` as immutable because it is also borrowed as mutable
 --> main.rs:6:20
  |
4 |     let (part1,part2) =&mut someTuple;
  |                        -------------- mutable borrow occurs here
5 | 
6 |     println!("{}", &someTuple.0);
  |                    ^^^^^^^^^^^^ immutable borrow occurs here
...
9 |     println!("{}", part1);
  |                    ----- mutable borrow later used here

error: aborting due to previous error


“除非 someTuple 的内容是可复制的”,所以如果它们是可复制的,它会执行一件事,但如果不是,它会执行另一件事?这似乎是一个潜在的陷阱。 - Seph Reed
@SephReed 没错。这种区别在 Copy 的文档中有解释。简言之,对于 someTuple 中的每个成员,如果该成员实现了 Copy,那么无论是 someTuple 还是新绑定的变量(例如 part1part2)都将拥有各自的数据副本的所有权。如果成员没有实现 Copy,则所有权将转移到新变量,并允许您从 someTuple 访问数据。 - Brian61354270
有没有其他的符号表示法可以明确表明你是在使用复制还是借用? - Seph Reed
@SephReed 我不确定你的意思。无论你是获取指向某个东西的指针(又称“借用”)还是复制或移动该物体本身,都是明确的。我上面提到的区别仅适用于复制某些内容的情况,而区别本身在于旧所有者是否保持有效(在复制的情况下)或无效(在移动的情况下)。如果有具体的例子让你感觉不清楚是进行了借用还是移动,你可以提出一个新问题。 - Brian61354270

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