英文:
Why can I create two live &mut to the same variable using &mut (**ref)?
问题
以下是您提供的代码片段的翻译:
考虑以下两个代码片段:
fn main() {
let mut foo = 1;
let mut borrower = &mut foo;
let mut borrower2 = &mut foo; // 错误:无法同时多次可变借用 `foo`
*borrower2 = 2;
*borrower = 3;
println!("{}",&foo);
}
这不会编译,如预期的那样,因为您不能对同一变量拥有两个可变引用。然而,如果我将有问题的行替换为以下内容,它可以工作:
fn main() {
let mut foo = 1;
let mut borrower = &mut foo;
let rr = &mut borrower;
let borrower2 = &mut (**rr);
*borrower2 = 2; // 我可以使用这两个来修改 foo
*borrower = 3; // 这些借用明显重叠了吗?
println!("{}",foo);
}
当颠倒两个赋值时,将会出现以下错误:
错误[E0506]: 无法对 `*borrower` 进行赋值,因为它已被借用
--> src/main.rs:48:5
|
45 | let rr = &mut borrower;
| ------------- 在此处借用了 `*borrower`
...
48 | *borrower = 3;
| ^^^^^^^^^^^^^ 在此处对 `*borrower` 赋值,但它已经被借用
49 | *borrower2 = 2;
| -------------- 此处后续使用了借用
希望这有帮助。如果您需要进一步的解释或翻译,请告诉我。
英文:
Consider the following two code snippet:
fn main() {
let mut foo = 1;
let mut borrower = &mut foo;
let mut borrower2 = &mut foo; // error: cannot borrow `foo` as mutable more than once at a time
*borrower2 = 2;
*borrower = 3;
println!("{}", foo);
}
This does not compile, as expected, because you can't have two mutable references to the same variable. However, if I replace the offending line as in the following, it works:
fn main() {
let mut foo = 1;
let mut borrower = &mut foo;
let rr = &mut borrower;
let borrower2 = &mut (**rr);
*borrower2 = 2; // I can use both of these to mutate foo
*borrower = 3; // Shouldn't these borrows clearly overlap?
println!("{}", foo);
}
When reversing the two assignments, the following error is given:
error[E0506]: cannot assign to `*borrower` because it is borrowed
--> src/main.rs:48:5
|
45 | let rr = &mut borrower;
| ------------- `*borrower` is borrowed here
...
48 | *borrower = 3;
| ^^^^^^^^^^^^^ `*borrower` is assigned to here but it was already borrowed
49 | *borrower2 = 2;
| -------------- borrow later used here
答案1
得分: 6
是的,确实,如果Rust没有任何其他机制,那你可能已经知道的那个机制就会导致错误。然而,你可能已经猜到,对于重新借用,还有一个额外的规则。实际上,要找到关于这个特性的适当文档确实很困难,一个快速而不太正式的谷歌搜索导致我找到的最佳文档是这个。我认为这是因为堆栈借用是一个年轻的概念,相关的工具仍然在进行中(例如,像miri这样的工具),因此文档也相对较新。
重新借用与堆栈借用密切相关。其思想是,在第二个示例中,&mut **r
不仅仅是对foo
的全新借用,它是通过r
创建的借用。因此,Rust将认为你在&mut **r
有效的期间使r
无效,一旦&mut **r
结束,r
就重新变为有效。
一个好的理解方式是将每个分配(为了简单起见)看作是一个堆栈。每次你对分配的数据进行可变借用时,你都会将该借用添加到堆栈中。当然,你不能同时进行两次可变借用,所以在创建一个新的可变借用时,堆栈必须为空。但是,如果你有一个活跃的可变借用,你可以通过将其推到堆栈顶部来从该独占借用中创建一个新的可变借用。请注意,你始终只能使用活跃的可变借用,即堆栈顶部的那个。当借用的生命周期结束时,它将从堆栈中弹出。重要的是你不能弹出不在堆栈顶部的东西,这就意味着:重新借用的生命周期最多不能超过其父借用的生命周期。违反此规则将导致UB,并且这是miri检查的事情之一。
实际上,当你允许将分配或可变借用分割为不重叠的可变借用时(例如,使用split_at_mut
),情况可能会变得更加复杂。在这种情况下,借用不像堆栈一样增长,而是像一棵树一样增长,你只能“弹出”叶子,只能将叶子添加到现有的叶子中...
英文:
Yes, indeed, if Rust didn't have any other mechanisms that the one you probably already know, this would be an error. However, as might have guessed, there is an additional rule, in case of reborrowing. It's actually genuinely hard to find proper documentation on this feature, the best a quick and dirty google search led me to is this. I think this is because stacked borrows is a young idea, the related tooling is still WIP (like miri for instance), and so documentation is quite fresh too.
Reborrowing is closely related to stacked borrows. The idea is that, in the second example, &mut **r
is not just a brand new borrow to foo
, it's a borrow that was made through r
. For this reason, Rust will consider that you invalidated r
for the time &mut **r
is valid, and once &mut **r
ends, r
begins valid again.
A nice way to think about this is to see each allocation (to keep it simple) as a stack. Each time you take a mutable borrow to the data contained in the allocation, you add that borrow to the stack. Of course, you can't take two mutable borrows at once, so the stack must be empty when you create a new mutable borrow. But then, if you have an active mutable borrow, you can create a new mutable borrow from that exclusive borrow by pushing it on top of the stack. Note that you can always only use the active mutable borrow, which is the one on top of the stack. When the lifetime of a borrow ends, it is popped from the stack. The important thing is that you can't pop something that is not on top of the stack, which translates to: a reborrow must live at most as long as its parent borrow. Breaking this rule is UB, and is (one of) the things that miri checks.
In fact, things can become a little bit more funky when you allow splitting an allocation or a mutable borrow into non-overlapping mutable borrows (like with split_at_mut
). In this case, borrows don't grow like a stack, but like a tree, where you can only "pop" leaves, and can only add leaves to existing leaves...
答案2
得分: 1
这被称为重新借用。重新借用是指当可变引用的类型发生变化时发生的情况。来自文档:
> let mut foo: i32 = 22;
> let r_a: &'a mut i32 = &'a mut foo;
> let r_b: &'b mut i32 = &'b mut r_a;
>
> use(r_b);
>
> 在这种情况下,r_a的支持前缀是r_a和r_a
> (因为r_a是可变引用,我们进行递归)。其中只有一个,
> r_a,是一个解引用左值,而被解引用的引用r_a的生命周期为'a。我们会添加约束条件'a: 'b,因此
> 确保只要r_b在使用中,foo就被认为是借用的。
> 没有这个约束,生命周期'a会在第二次
> 借用之后结束,因此即使r_b
> 仍然可以用来访问foo,foo也会被认为是未借用的。
>
> 示例2。现在考虑一个双重间接的情况:
>
> let mut foo: i32 = 22;
> let mut r_a: &'a i32 = &'a foo;
> let r_b: &'b &'a i32 = &'b r_a;
> let r_c: &'c i32 = &'c **r_b;
> // 这里被认为是借用的是什么?
> use(r_c);
>
> 就像之前一样,只要r_c在使用,foo就被
> 认为是借用的。但是,r_a变量又如何呢?答案是否定的:一旦r_c初始化,r_a的值就不再重要,甚至可以(例如)用新值覆盖r_a,尽管foo仍然被认为是借用的。这个结果是根据我们的重新借用规则得出的:
> r_b的支持路径只是r_b。我们不会再添加更多的路径,因为这个路径已经是r_b的解引用,而*r_b的类型是共享引用类型&'a i32。因此,我们会添加一个重新借用约束:'a: 'c。这个约束确保只要r_c
> 在使用中,foo的借用仍然有效,但r_a的借用
> (其生命周期为'b)可以到期。
英文:
This is called reborrowing. Reborrowing happens when a mutable reference's type changes. From docs:
> let mut foo: i32 = 22;
> let r_a: &'a mut i32 = &'a mut foo;
> let r_b: &'b mut i32 = &'b mut *r_a;
>
> use(r_b);
>
> In this case, the supporting prefixes of *r_a are *r_a and r_a
> (because r_a is a mutable reference, we recurse). Only one of those,
> *r_a, is a deref lvalue, and the reference r_a being dereferenced has the lifetime 'a. We would add the constraint that 'a: 'b, thus
> ensuring that foo is considered borrowed so long as r_b is in use.
> Without this constraint, the lifetime 'a would end after the second
> borrow, and hence foo would be considered unborrowed, even though *r_b
> could still be used to access foo.
>
> Example 2. Consider now a case with a double indirection:
>
> let mut foo: i32 = 22;
> let mut r_a: &'a i32 = &'a foo;
> let r_b: &'b &'a i32 = &'b r_a;
> let r_c: &'c i32 = &'c **r_b;
> // What is considered borrowed here?
> use(r_c);
>
> Just as before, it is important that, so long as r_c is in use, foo is
> considered borrowed. However, what about the variable r_a: should it
> considered borrowed? The answer is no: once r_c is initialized, the
> value of r_a is no longer important, and it would be fine to (for
> example) overwrite r_a with a new value, even as foo is still
> considered borrowed. This result falls out from our reborrowing rules:
> the supporting paths of **r_b is just **r_b. We do not add any more
> paths because this path is already a dereference of *r_b, and *r_b has
> (shared reference) type &'a i32. Therefore, we would add one reborrow
> constraint: that 'a: 'c. This constraint ensures that as long as r_c
> is in use, the borrow of foo remains in force, but the borrow of r_a
> (which has the lifetime 'b) can expire.
答案3
得分: 1
免责声明:对迄今为止的回答不太满意。我认为更深入的答案将更有价值。
所有权与借用
Rust 中处理值(和引用)的两个基本概念是所有权和借用:
- 所有权可以转移:值从一个变量转移到另一个变量。
- 所有权可以“临时借出”:创建对该值的引用(可变或不可变),进行借用。
还可以克隆(或复制)一个值,这种情况下会创建一个新的值,独立于原始值。在这种情况下,不会发生所有权的转移,并且克隆(或复制)的值的借用在新值可用时结束。
复制引用
不可变(共享)引用是可复制的(它们实现了Copy
特性),因为在任何时刻,你可以拥有任意数量的不可变引用指向给定值。
然而,可变(唯一)引用不可复制,否则它们就不会是唯一的。
这意味着将引用分配给变量将具有不同的属性,具体取决于它是否是不可变的:
let x = 3;
let y = &x;
let z = y; // z 是 y 的复制
let mut a = 7;
let b = &mut a;
let c = b; // b 被“移动”到 c,变量 b 之后不能再使用。
后者是能够“临时”借用引用(或可变引用)的关键动机之一,例如,用于进行函数调用:
fn foo(r: &mut i32) {
*r += 1;
}
fn main() {
let mut a = 7;
let b = &mut a;
foo(b);
println!("{b}"); // 预期错误,`b` 已被移出。
}
进入再借用
解决上述问题的方法是“再借用”,即从其“引用”中借用“引用”的“引用”的值,临时。
关键思想是它的工作方式与从值中借用非常相似。当从值中借用时:
- 你从中借用的变量保持不变。
- 在借用期间,变量不可访问——在可变借用的情况下——借用的生命周期(引用的生命周期的子集)内。
因此,再借用允许相同的关键思想:
- 你从中借用的引用保持不变。
- 在借用期间,你从中借用的引用不可访问——在可变借用的情况下——借用的生命周期内。
再借用的语法有点特殊。你不能写&reference
,因为那会创建一个对引用的引用,这是有效的,但不是你想要的。因此,你只能写&*reference
,以指示你想要引用reference
引用的内容本身。
由于这个语法相当冗长,不太符合人体工程学的原则,通常再借用会自动为你完成。以前面的例子为例:
fn foo(r: &mut i32) {
*r += 1; // 再借用 1
}
fn main() {
let mut a = 7;
let b = &mut a;
foo(b); // 再借用 2
println!("{b}");
}
从底部开始,当调用带有可变引用的函数时,编译器会自动插入&mut *
,因此实际上即使b
不能复制,该示例也不会导致编译器错误。
同样,*r += 1
实际上是再借用。这次*
被手动插入,然后编译器隐式应用了&mut
,因为它重写表达式为(*r).add_assign(1)
,其中add_assign
接受&mut self
。
再借用无处不在,只是它如此自动化,以至于你可能从未注意到它。
应用再借用
让我们来看看你的示例,弄清楚发生了什么:
fn main() {
let mut foo = 1;
let mut borrower = &mut foo;
let rr = &mut borrower; // (1)
let borrower2 = &mut (**rr); // (2)
*borrower2 = 2; // (3)
*borrower = 3; // (4)
println!("{foo}");
}
按顺序:
- 创建对
foo
的可变引用的可变引用:rr
的类型是&mut &mut i32
。borrower
现在被借用了。
- 创建对
foo
的可变引用:borrower2
的类型是&mut i32
。rr
现在被“再借用”了。
- 分配给
borrower2
引用的值。 - 分配给
borrower
引用的值。
为什么(4)是允许的?
简短的答案是rr
对borrower2
的借用的生命周期已经结束,而且rr
对borrower
的借用的生命周期也已经结束,因此borrower
不再被借用。
较长的答案是,最初 Rust 编译器会考虑一个变量被借用的时间,通常是直到创建引用的范围结束。这是不灵活的,需要引入额外的范
英文:
Disclaimer: not quite satisfied with the answers so far. I think a more in-depth answer would be more valuable.
Ownership & Borrowing
The two cornerstones concept of the handling of values (and references) in Rust are ownership and borrowing:
- Ownership can be transferred: the value is moved from one variable to another.
- Ownership can be "lent out" temporarily: a reference (mutable or not) to the value is created, borrowing it.
It is also possible to clone (or copy) a value, in which case a new value is created, independent from the original. In this case, no transfer of ownership occurs, and the borrow of the value that is cloned (or copies) ends by the time the new value is available.
Copying References
Immutable (shared) references are copyable (they implement the Copy
trait), since you can have any number of immutable references to a given value at any moment in time.
Mutable (unique) references are NOT, however, as otherwise, they'd wouldn't be unique.
This means that assigning a reference to a variable will have different properties, depending on whether it's immutable or not:
let x = 3;
let y = &x;
let z = y; // z is a Copy of y
let mut a = 7;
let b = &mut a;
let c = b; // b is _moved_ to c, the variable b cannot be used afterwards.
The latter is a key motivation for being able to temporarily borrow a reference (or mutable reference) of a
from b
, for example, to make a function call:
fn foo(r: &mut i32) {
*r += 1;
}
fn main() {
let mut a = 7;
let b = &mut a;
foo(b);
println!("{b}"); // Expected error, `b` was moved out.
}
Enter Re-borrowing
The solution to the above issue is the idea of re-borrowing, which is borrowing the referred value out of its reference, temporarily.
The key idea is that it works very similarly to borrowing from a value. When you borrow from a value:
- The variable you borrow from is intact.
- The variable is inaccessible -- in case of mutable borrowing -- for the lifetime of the borrow (a subset of the lifetime of the reference).
And thus re-borrowing allows the same key idea:
- The reference you borrow from is intact.
- The reference you borrow from is inaccessible -- in cast of mutable borrowing -- for the lifetime of the borrow.
The syntax of re-borrowing is a bit special. You can't write &reference
as that would create a reference to the reference, which is valid, but not quite what you want. Thus you are left writing &*reference
to indicate that you want a reference to what reference
refers to itself.
Due to this syntax being quite a bit verbose, and not too ergonomics, re-borrowing is usually done automatically for you. Taking our previous example:
fn foo(r: &mut i32) {
*r += 1; // Re-borrow 1
}
fn main() {
let mut a = 7;
let b = &mut a;
foo(b); // Re-borrow 2
println!("{b}");
}
Starting from the bottom, when a function taking a mutable reference is called with a mutable reference, the compiler automatically inserts &mut *
, so that in fact the example does not lead to a compiler error even though b
cannot be copied, only moved.
Similarly, the *r += 1
is actually re-borrowing. This time the *
is inserted manually, and the compiler then applies the &mut
implicitly as it rewrites the expression to (*r).add_assign(1)
where add_assign
takes &mut self
.
Re-borrowing is everywhere, it's just so automatic you probably never noticed.
Applying re-borrowing
Let's examine your examples to figure out what's going on:
fn main() {
let mut foo = 1;
let mut borrower = &mut foo;
let rr = &mut borrower; // (1)
let borrower2 = &mut (**rr); // (2)
*borrower2 = 2; // (3)
*borrower = 3; // (4)
println!("{}", foo);
}
In order:
- Creates a mutable reference to a mutable reference to
foo
:rr
is of type&mut &mut i32
.borrower
is now borrowed.
- Create a mutable reference to
foo
:borrower2
is of type&mut i32
.rr
is now reborrowed.
- Assign to the value referred to by
borrower2
. - Assign to the value referred to by
borrower
.
Why is (4) allowed?
The short answer is that the lifetime of the borrow of rr
by borrower2
ended, and the lifetime of the borrow of borrower
by rr
ended, and thus borrower
is no longer borrowed.
The long answer is that originally the Rust compiler would consider a variable borrowed for the lifetime of its reference, which was typically until the end of the scope in which the reference was created. This was inflexible, and required introducing extra scopes:
fn main() {
let mut foo = 1;
let mut borrower = &mut foo;
{
let rr = &mut borrower; // (1)
let borrower2 = &mut (**rr); // (2)
*borrower2 = 2; // (3)
}
*borrower = 3; // (4)
println!("{}", foo);
}
To make our life easier, Non-Lexical Lifetimes (NLL) were implemented, giving the latitude to the compiler to end borrows earlier than the end of the scope of the references that created the borrow. In general, this means that the borrow only lasts until the last use of the reference.
And therefore your example works without the (manual) extra scope.
What if you reverse the assignments? That is:
fn main() {
let mut foo = 1;
let mut borrower = &mut foo;
let rr = &mut borrower; // (1)
let borrower2 = &mut (**rr); // (2)
*borrower = 3; // (A)
*borrower2 = 2; // (B)
println!("{}", foo);
}
Well, then NLL doesn't save you any longer, since it only contracts the tail of the borrow, not its head. It could theoretically only borrow from first to last use, rather than from creation to last use, but it doesn't, and therefore:
borrower2
is last used at (B).- Thus,
rr
must be borrowed from (2) to (B). - Thus,
borrower
must be borrowed from (1) to (B). - Thus,
borrower
is borrowed at (A) -- and it's an error to try to re-borrow it again.
All clear?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论