Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it allowed to have both immutable and mutable borrows of a vector of numeric types in one expression?

Tags:

rust

a is a Vec<i32> which can be mutably and immutably referenced in one expression:

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

I thought this compiled because i32 implements Copy, so I created another type that implements Copy and compiled it like the first example, but it fails:

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];
}

playground

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
  1. Why does my MyNum not behave in the same way as i32 even though it implements Copy?
  2. Why can the vector be mutably and immutably referenced in one expression?
like image 373
Yohei Avatar asked May 11 '20 14:05

Yohei


1 Answers

I believe the thing you're seeing here is that primitive types do not actually call their std::ops equivalents. Those std::ops may just be included for seamless trait extensions, etc. I think the blog post Rust Tidbits: What Is a Lang Item? partially explains this.

I exported the MIR of your example that works with primitive types. I got:

    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
    }

I had a lot of difficulty exporting the MIR for the code that was erroring. Outputting MIR without borrow checking is new to me and I couldn't figure out how to do it.

This playground has a very similar thing, but compiles :)

It gives me an actual call to 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>)) }
    }

How does the primitive case pass the borrow checker? Since add_assign is not called, the immutable reference can be dropped before the mutable reference is required. The MIR simply dereferences the needed location earlier on and passes it through by value.

    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
        ...
    }
like image 151
chub500 Avatar answered Sep 29 '22 10:09

chub500