为什么允许在一个表达式中同时具有数值类型向量的不可变和可变借用?

Yoh*_*hei 13 rust

a是一个Vec<i32>可以在一个表达式中可变且不可变地引用的 a :

fn main() {
    let mut a = vec![0, 1];
    a[0] += a[1]; // OK
}
Run Code Online (Sandbox Code Playgroud)

我认为这个编译是因为i32implements Copy,所以我创建了另一种类型,Copy它像第一个例子一样实现和编译它,但它失败了:

use std::ops::AddAssign;

#[derive(Clone, Copy, PartialEq, Debug, Default)]
struct MyNum(i32);

impl AddAssign for MyNum {
    fn add_assign(&mut self, rhs: MyNum) {
        *self = MyNum(self.0 + rhs.0)
    }
}

fn main() {
    let mut b = vec![MyNum(0), MyNum(1)];
    b[0] += b[1];
}
Run Code Online (Sandbox Code Playgroud)

操场

error[E0502]: cannot borrow `b` as immutable because it is also borrowed as mutable
  --> src/main.rs:14:13
   |
14 |     b[0] += b[1];
   |     --------^---
   |     |       |
   |     |       immutable borrow occurs here
   |     mutable borrow occurs here
   |     mutable borrow later used here
Run Code Online (Sandbox Code Playgroud)
  1. 为什么我的MyNum行为与i32即使它实现的方式不同Copy
  2. 为什么可以在一个表达式中可变且不可变地引用向量?

chu*_*500 5

我相信你在这里看到的是原始类型实际上并没有调用它们的std::ops等价物。这些std::ops可能只是用于无缝特征扩展等。我认为博客文章Rust Tidbits: What Is a Lang Item? 部分解释了这一点。

我导出了适用于原始类型的示例的 MIR 。我有:

    bb5: {
        StorageDead(_9);                 // bb5[0]: scope 1 at src/main.rs:6:8: 6:9
        _10 = CheckedAdd((*_8), move _5); // bb5[1]: scope 1 at src/main.rs:6:5: 6:17
        assert(!move (_10.1: bool), "attempt to add with overflow") -> [success: bb6, unwind: bb4]; // bb5[2]: scope 1 at src/main.rs:6:5: 6:17
    }
Run Code Online (Sandbox Code Playgroud)

我在为出错的代码导出 MIR 时遇到了很多困难。在没有借用检查的情况下输出 MIR 对我来说是新的,我不知道该怎么做。

这个操场有一个非常相似的东西,但编译:)

它给了我一个实际调用add_assign

    bb3: {
        _8 = _9;                         // bb3[0]: scope 1 at src/main.rs:14:5: 14:9
        StorageDead(_10);                // bb3[1]: scope 1 at src/main.rs:14:8: 14:9
        StorageLive(_11);                // bb3[2]: scope 1 at src/main.rs:14:14: 14:22
        (_11.0: i32) = const 1i32;       // bb3[3]: scope 1 at src/main.rs:14:14: 14:22
                                         // ty::Const
                                         // + ty: i32
                                         // + val: Value(Scalar(0x00000001))
                                         // mir::Constant
                                         // + span: src/main.rs:14:20: 14:21
                                         // + literal: Const { ty: i32, val: Value(Scalar(0x00000001)) }
        _7 = const <MyNum as std::ops::AddAssign>::add_assign(move _8, move _11) -> [return: bb5, unwind: bb4]; // bb3[4]: scope 1 at src/main.rs:14:5: 14:22
                                         // ty::Const
                                         // + ty: for<'r> fn(&'r mut MyNum, MyNum) {<MyNum as std::ops::AddAssign>::add_assign}
                                         // + val: Value(Scalar(<ZST>))
                                         // mir::Constant
                                         // + span: src/main.rs:14:5: 14:22
                                         // + literal: Const { ty: for<'r> fn(&'r mut MyNum, MyNum) {<MyNum as std::ops::AddAssign>::add_assign}, val: Value(Scalar(<ZST>)) }
    }
Run Code Online (Sandbox Code Playgroud)

原始案例如何通过借用检查器?由于add_assign未调用,因此可以在需要可变引用之前删除不可变引用。MIR 只是提前取消引用所需的位置并按值传递它。

    bb3: {
        _5 = (*_6);                      // bb3[0]: scope 1 at src/main.rs:6:13: 6:17
        StorageDead(_7);                 // bb3[1]: scope 1 at src/main.rs:6:16: 6:17
        ...
    }
Run Code Online (Sandbox Code Playgroud)

  • 您可以相当可靠地导出 MIR,而无需使用 -Zdump-mir 标志每晚让borrowck 传递——这将创建一个包含一堆 mir 转储的目录。我通常只选择最后一个,例如在这种情况下,您可以查看 `mir_dump/rustc.main.002-001.SimplifyCfg-qualify-consts.after.mir`。有关更多详细信息,请参阅 https://rustc-dev-guide.rust-lang.org/mir/debugging.html。 (2认同)
  • @Yohei - 正如@trentcl 发现的 - 你可以转换为切片,然后该行就可以工作。`让切片= b.as_mut_slice(); 切片[0] += 切片[1];`。 (2认同)