MIRI error, But I have removed mutable reference to avoid UB

I am not good at English. Sorry if there are any funny expressions.


I know that multiple mutable references to the same value result in UB (undefined behavior).

So I tried keeping one as a mutable reference and one as a pointer (In the actual code, one is a part of the other, but I'm simplifying here). Then, I thought that when converting back from the pointer to the mutable reference, I just drop the first mutable reference.

The code ran as expected. However, verification on MIRI resulted in error (There was a link about error detail, but I couldn't understand it even after reading it).

Please help me.

#![allow(dead_code)]

#[test]
fn test() {
    let mut var = ValHolder { val: MyVal(1) };
    TwoWayMutRef::new(&mut var).method();
}

pub struct ValHolder {
    val: MyVal,
}

pub struct TwoWayMutRef<'a> {
    mut_ptr: *mut MyVal,
    mut_ref: Option<&'a mut MyVal>,
}

impl<'a> TwoWayMutRef<'a> {
    pub fn new(vh: &'a mut ValHolder) -> Self {
        Self {
            mut_ptr: (&mut vh.val) as *mut _,
            mut_ref: Some(&mut vh.val), // ๐Ÿ‘ˆ If `None`, everything is OK.
        }
    }

    pub fn method(&mut self) {
        // ๐Ÿ—‘๏ธ Remove mutable reference (to avoid mutable reference alias UB).
        Option::take(&mut self.mut_ref);

        // โŒ Restore mutable reference from pointer (MIRI reports UB. why?).
        let val = unsafe { &mut *self.mut_ptr };
        val.do_something();
    }
}

struct MyVal(i32);
impl MyVal {
    fn do_something(&mut self) {}
}

MIRI Error excerpt

test test ... error: Undefined Behavior: trying to retag from <145113> for Unique permission at alloc45233[0x0], but that tag does not exist in the borrow stack for this location
  --> src\lib.rs:31:28
   |
31 |         let val = unsafe { &mut *self.mut_ptr };
   |                            ^^^^^^^^^^^^^^^^^^ this error occurs as part of retag at alloc45233[0x0..0x4]
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <145113> was created by a SharedReadWrite retag at offsets [0x0..0x4]
  --> src\lib.rs:21:22

With the stacked borrow model, this point invalidates all previously made mutable references to vh.val and the pointers derived from it.

This should work

        let mut_ref = &mut vh.val;
        Self {
            mut_ptr: mut_ref as *mut _,
            mut_ref: Some(mut_ref),
        }
1 Like

Wow! This is like magic.

Whether or not to declare (bind) intermediate variables changes the behaviorโ€”that's always a blind spot for me in Rust. In other languages, this is just a refactoring topic...

Anyway thank you very much!

It is not about the intermediate variables. It is about having a single stack of mutable borrows and pointer provenance. Mutable borrows make huge promises about who may access the target and when. My advice is to avoid mixing mutable references and pointers.

3 Likes

Thanks for the correction. When I first started learning Rust, I was really surprised by variable bindings, so I assumed this was another instance of that. But it seems to be more about the origin of the reference instead. Looks like I need to keep studying.