为什么Rust在安全的情况下不允许多个可变借位?

ne5o7dgx  于 2022-12-04  发布在  其他
关注(0)|答案(1)|浏览(119)

我很难理解为什么Rust的借用检查器不允许多个可变借用,而这样做是安全的。
让我们给予一个例子:

fn borrow_mut(s : &mut String) {
    s.push_str(" world!");
    println!("{}", s);
}

fn main() {
    let mut s = String::from("hello");
    let rs : &mut String = &mut s;

    // second mutable borrow
    borrow_mut(&mut s);

    println!("{rs}");
}

此代码无法编译,并显示以下消息:

error[E0499]: cannot borrow `s` as mutable more than once at a time
  --> main.rs:11:16
   |
8  |     let rs : &mut String = &mut s;
   |                            ------ first mutable borrow occurs here
...
11 |     borrow_mut(&mut s);
   |                ^^^^^^ second mutable borrow occurs here
12 | 
13 |     println!("{rs}");
   |                -- first borrow later used here

rs指向堆栈帧中String类型的变量。String包含堆中内存的指针。因此,即使字符串在borrow_mut()中重新分配其数据,两个指针仍然有效,因此这段代码应该是安全的。
有人能解释为什么借入检查器阻止多个可变借入,即使它是安全的吗?

dtcbnfnu

dtcbnfnu1#

这是为了线程安全,以避免数据竞争。如果两个这样的可变借用可以存在,那么两个执行线程都可以尝试修改原始数据。如果它们这样做,各种各样的恶劣竞争条件可能会出现,例如,如果两个线程都试图追加到字符串:

  • 保存数据的底层数组可能会被重新分配两次,其中一次会泄漏
  • 由于检查时间/使用时间问题,追加的数据可能会写入边界之外
  • 您可能会导致长度和容量的定义不一致
  • 在某些体系结构和数据大小上,撕裂可能意味着读取单个逻辑值,一半作为旧版本,一半作为更新值(产生的内容很容易与旧 * 或 * 新值无关)
  • 等等。

借用作为一种语言特性意味着函数可以暂时将其唯一的可变所有权移交给其他函数;而另一个函数持有借位,则原始对象不能通过 * 除了 * 可变借位之外任何东西来访问。这还意味着对于不可变借位,它可以防止可变借位,后者可能会导致不可变借位的读操作和可变借位的写操作之间的竞争。借位检查器阻止您启动修改s的线程,然后从主线程调用borrow_mut,当两个线程同时修改s时,它们产生垃圾或使程序崩溃。
需要说明的是,在Rust的某个未来版本中,通过一个高级的借用检查器,这段代码 * 可以 * 工作(您编写的代码本质上不会做任何不安全的事情)。但是完全分析深层代码路径以确保不会发生任何不安全的事情是很困难的,相对来说,要制定更严格的规则(如果他们确信它不会对语言设计施加限制,那么将来可能会放松限制)。如果将已有的一个可变借位传递到borrow_mut中,代码就可以正常工作;您的代码不会因为采用RustWay ™而变得更糟。

相关问题