1. 25
  1. 4

    TL;DR for people who already know a lot about C aliasing rules and get Rust reference semantics:

    • Rust’s optimizer treats mutable references as restrict, because they literally are
    • unsafe Rust doesn’t use C/C++ strict aliasing rules, and that’s good for unsafe Rust

    The example about converting raw pointers to a mutable reference confused me a little. It seems like making a &mut out of a raw pointer applies safe Rust &mut semantics to that pointer, effectively yoinking the pointer permanently out of unsafe land. Presumably because raw pointers don’t have any notion of borrowing, so there’s no way for dropping the &mut to return the mutable borrow? Is that correct? Does that mean when writing unsafe code you always need to create a new raw pointer from the latest safe reference if you want to again expose that pointer as a &mut? Does the same thing happen with shared references, or do you just have to make sure you don’t create any shared references that alias mutable references?

    1. 3

      Presumably because raw pointers don’t have any notion of borrowing, so there’s no way for dropping the &mut to return the mutable borrow? Is that correct? Does that mean when writing unsafe code you always need to create a new raw pointer from the latest safe reference if you want to again expose that pointer as a &mut?

      If I understand you (and rust) correctly, no.

      &mut guarantees to the optimizer (and the rest of the compiler) that, briefly, restrict semantics apply (nothing will access the thing it points to unless that thing is accessing it through it, or something derived from it). It’s fine to convert *mut to &mut and expose those, provided you respect that guarantee. E.g. the following code snippet is correct

      // Assume  that `raw` is a valid ptr, pointing to valid 
      // data and that I the programmer know that nothing is aliasing it.
      struct OpaquePtr { raw: *mut i32 }
      impl OpaquePtr {
          // Lifetime could be omitted, but it's here for clarity.
          fn as_mut<'a>(&'a mut OpaquePtr) -> &'a mut i32 {
              // SAFETY: As noted above raw is a valid ptr, and nothing is aliasing it.
              // Because we mutably borrow the `OpaquePtr` for the entire duration that
              // the reference we returned is live, nothing can alias the returned 
              // value via the `OpaquePtr` either.
              unsafe{ &mut *self.raw }
          }
      }
      
      1. 1

        Ah, you are right, I misread the example in the video. A raw pointer was never converted to a mutable reference, rather a mutable reference was made from the same “origin” as the raw pointer. So that means mutably borrowing the “origin” of a raw pointer invalidates that raw pointer. Is that correct?

        The example I had trouble with was this:

        fn main() {
            let mut x: i32 = 1;
            let x_ptr = &x as *const i32;
            _ = &mut x;
            println!("{}", unsafe { *x_ptr }); // Miri detects UB here
        }
        

        And the way I misread it was this:

        fn main() {
            let mut x: i32 = 1;
            let x_ptr = &mut x as *mut i32;
            _ = unsafe { &mut *x_ptr };
            println!("{}", unsafe { *x_ptr });
        }
        

        Which Miri takes no issue with.

        1. 1

          Yeah, your second example is treated as a reborrow of x_ptr.

          1. 1

            We’re venturing into the realms of “the details of the memory model aren’t entirely specified yet”, but that sounds correct to me with the caveat that the explanation for why it’s right might change.

        2. 2

          If you want to learn more about exactly what rules Miri is enforcing, the term to search for is “stacked borrows”. I think the most up-to-date place to read up on this is: https://plv.mpi-sws.org/rustbelt/stacked-borrows/

          1. 1

            Rust’s optimizer treats mutable references as restrict

            Rust’s shared references are also effectively restrict pointers. As with const restrict pointers in C, the compiler is allowed to assume that nothing else will modify the pointed-to value while the reference is alive.

          2. 3

            A fantastic talk, just like your previous one (link to lobste.rs discussion). I said this last time too, but: as someone who got into systems programming through Rust, seeing you translate back and forth between C/C++ and Rust is very instructive.