为什么Rust编译器错误为“0”;不能作为不可变借用,因为它也是作为可变借用的;将变量移动到作用域后?
在阅读了Rust的作用域和引用之后,我编写了一个简单的代码来测试它们为什么Rust编译器错误为“0”;不能作为不可变借用,因为它也是作为可变借用的;将变量移动到作用域后?,rust,reference,mutable,Rust,Reference,Mutable,在阅读了Rust的作用域和引用之后,我编写了一个简单的代码来测试它们 fn main(){ //1.定义一个字符串 让mut a=String::from(“great”); //2.获取可变引用 设b=&mut a; b、 推_街(“微风”); println!(“b={:?}”,b); //3.A作用域:退出此作用域后c无效 { 设c=&a; println!((“c={:?}”,c); } //4.使用可变引用作为不可变引用的作用域 //不再有效。 println!((“b={:?}”,b
fn main(){
//1.定义一个字符串
让mut a=String::from(“great”);
//2.获取可变引用
设b=&mut a;
b、 推_街(“微风”);
println!(“b={:?}”,b);
//3.A作用域:退出此作用域后c无效
{
设c=&a;
println!((“c={:?}”,c);
}
//4.使用可变引用作为不可变引用的作用域
//不再有效。
println!((“b={:?}”,b);//src/main.rs:12:17
|
6 |设b=&mut a;
|----可变借用发生在这里
...
12 |设c=&a;
|^^此处发生不可变借用
...
18 | println!(“b={:?},b);//你的问题是为什么编译器不允许c
引用已经以可变方式借用的数据。作为一个人,我希望从一开始就不允许这样做
但是-当您注释掉最后一个println!()
时,代码编译正确。这大概就是导致您得出允许使用别名的结论的原因,“只要可变项不在同一范围内”。我认为该结论不正确,原因如下
的确,在某些情况下,子作用域中的引用允许使用别名,但这需要进一步的限制,例如通过结构投影缩小现有引用的范围。(例如,给定一个let r=&mut点
,您可以编写let rx=&mut r.x
,即临时可变借用可变借用数据的子集。)但这里的情况并非如此。这里的c
是一个全新的共享引用,用于引用b
已经可变引用的数据。这是绝对不允许的,但它可以编译
答案在于编译器对(NLL)的分析。当您注释掉最后一个println!()
时,编译器注意到:
b
不是Drop
,因此,如果我们假装它在最后一次使用后很快就被丢弃了,那么没有人能观察到区别
b
在第一次println!()
之后不再使用
因此,NLL在第一个println!()
之后插入一个不可见的drop(b)
,从而允许在第一个位置引入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);
}
要明确回答您的问题:
即使在显式地将c
移动到作用域中之后,为什么Rust编译器会产生此错误消息
因为c
不允许与b
一起存在,不管它是一个内部作用域。允许它存在的情况是,编译器可以证明b
从未与c
并行使用,甚至在构建c
之前就可以安全地删除它。在这种情况下sing是“允许的”,因为尽管b
“在范围内”,但没有实际的别名-在生成的MIR/HIR级别上,引用数据的只是c
。您已经失去了“监管链”;c
使b
无效。相反,请执行让c=&*b;
@Shepmaster什么是一个真正的监管链Rust中的“监管链”?这对我来说是一个新术语,在阅读参考文献和范围时,我从未在文档中看到过。请对初学者保持友好的回答。Rust使用的不是真正的术语。
// causes compilation error for code above
impl Drop for Ref<'_> {
fn drop(&mut self) {
}
}