在阅读了 Rust 的作用域和引用之后,我编写了一个简单的代码来测试它们。
fn main() {
// 1. define a string
let mut a = String::from("great");
// 2. get a mutable reference
let b = &mut a;
b.push_str(" breeze");
println!("b = {:?}", b);
// 3. A scope: c is useless after exiting this scope
{
let c = &a;
println!("c = {:?}", c);
}
// 4. Use the mutable reference as the immutable reference's scope
// is no longer valid.
println!("b = {:?}", b); // <- why does this line cause an error?
}
据我所知:
在 3
中,c
是在一个范围内创建的,并且其中没有使用任何可变参数。因此,
当 c
超出范围时,很明显 c
将不再使用(如
它是无效的),因此 b
是一个可变引用,可以安全地用于
4
。
预期输出:
b = "great breeze"
c = "great breeze"
b = "great breeze"
Rust 产生以下错误:
error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
--> src/main.rs:12:17
|
6 | let b = &mut a;
| ------ mutable borrow occurs here
...
12 | let c = &a;
| ^^ immutable borrow occurs here
...
18 | println!("b = {:?}", b); // <- why does this line cause an error?
| - mutable borrow later used here
(这只是我认为正在发生的事情,可能是谬论)
似乎无论如何都不能使用任何可变引用(b
)
一旦创建了一个不可变的引用(c
)(或被 Rust
编译器),无论是否在范围内。
这很像:
<块引用>在任何给定时间,您都可以拥有一个可变引用或任何 不可变引用的数量。
这种情况类似于:
let mut a = String::from("great");
let b = &mut a;
b.push_str(" breeze");
println!("b = {:?}", b);
// ^ b's "session" ends here. Using 'b' below will violate the rule:
// "one mutable at a time"
// can use multiple immutables: follows the rule
// "Can have multiple immutables at one time"
let c = &a;
let d = &a;
println!("c = {:?}, d = {:?}", c, d);
println!("b = {:?}", b); // !!! Error
另外,只要我们使用不可变引用,原始对象 或引用变得“不可变”。如Rust Book所述:
<块引用>当我们有一个不可变的引用时,我们也不能有一个可变的引用。 不可变引用的用户不希望这些值突然出现 从他们下面变出来!
和
<块引用>...你可以有任何一个可变引用...
let mut a = String::from("great");
let b = &mut a;
b.push_str(" breeze");
println!("b = {:?}", b);
let c = &b; // b cannot be changed as long as c is in use
b.push_str(" summer"); // <- ERROR: as b has already been borrowed.
println!("c = {:?}", c); // <- immutable borrow is used here
所以上面的这段代码在某种程度上解释了@Shepmaster 的解决方案。
回到原始代码并移除作用域:
// 1. define a string
let mut a = String::from("great");
// 2. get a mutable reference
let b = &mut a;
b.push_str(" breeze");
println!("b = {:?}", b);
// 3. No scopes here.
let c = &a;
println!("c = {:?}", c);
// 4. Use the mutable reference as the immutable reference's scope
// is no longer valid.
println!("b = {:?}", b); // <- why does this line cause an error?
现在很清楚为什么这段代码有错误了。 Rust 编译器看到
我们使用的是可变的 b
(它是 a
的可变引用,
因此 a
变得不可变)同时还借用了不可变的
参考 c
。我喜欢称其为“中间没有不可变因素”。
或者我们也可以称之为“非夹心”。你不能拥有/使用可变的 介于“不可变声明”和“不可变使用”之间,反之亦然。
但这仍然没有回答为什么范围在这里失败的问题。
c
移入作用域之后,为什么 Rust
编译器生成此错误消息?答案 0 :(得分:4)
您的问题是为什么编译器不允许 c
引用已经可变借用的数据。我希望一开始就不允许这样做!
但是 - 当您注释掉最后一个 println!()
时,代码可以正确编译。大概这就是导致您得出允许别名的结论的原因,“只要可变参数不在同一范围内”。我认为这个结论是不正确的,原因如下。
虽然在某些情况下确实允许子作用域中的引用使用别名,但它需要进一步的限制,例如通过结构体投影缩小现有引用。 (例如,给定一个 let r = &mut point
,您可以编写 let rx = &mut r.x
,即临时可变地借用可变借用数据的子集。)但这里不是这种情况。这里的 c
是对已由 b
可变引用的数据的全新共享引用。这绝不应该被允许,但它可以编译。
答案在于编译器对 non-lexical lifetimes (NLL) 的分析。当您注释掉最后一个 println!()
时,编译器会注意到:
b
在第一个 Drop
之后不再使用。
因此 NLL 在第一个 b
之后插入一个不可见的 println!()
,从而允许首先引入 drop(b)
。只是因为隐含的 println!()
c
不会创建可变别名。换句话说,drop(b)
的范围被人为地缩短了由纯词法分析确定的范围(它相对于 c
和 b
的位置),因此非词法生命周期。
您可以通过将引用包装在新类型中来测试此假设。例如,这相当于您的代码,它仍然在编译时将最后一个 {
注释掉:
}
但是,如果我们仅仅为 println!()
实现 #[derive(Debug)]
struct Ref<'a>(&'a mut String);
fn main() {
let mut a = String::from("great");
let b = Ref(&mut a);
b.0.push_str(" breeze");
println!("b = {:?}", b);
{
let c = &a;
println!("c = {:?}", c);
}
//println!("b = {:?}", b);
}
,代码将不再编译:
Drop
明确回答您的问题:
<块引用>即使将 Ref
显式移动到作用域中,为什么 Rust 编译器会产生此错误消息?
因为从一开始就不允许 // causes compilation error for code above
impl Drop for Ref<'_> {
fn drop(&mut self) {
}
}
与 c
一起存在,无论是内部作用域。当它存在时,编译器可以证明 c
永远不会与 b
并行使用并且在 b
之前删除它是安全的甚至构建。在这种情况下,别名是“允许的”,因为尽管 c
“在范围内”,但没有实际别名 - 在生成的 MIR/HIR 级别上,只有 c
引用了数据。>