Why the code compiles?

The following code compiles:

use std::mem;

fn main() {
    let mut x = [1, 2, 3];
    let y: &mut i32 = unsafe { mem::transmute(x.as_mut_ptr()) }; 
    let xx = &x;
    *y = 9;

    println!("{}, {}", xx[0], *y)
}

While, to my understanding, x.as_mut_ptr() will borrow x as &mut, It's lifetime at least as same as y.

So, when y is still alive, the let xx = &x should not allowed.

Ok, I got what happens.

x.as_mut_ptr() returns *mut _, which has nothing about lifetimem and &mut x dropped just before let xx = &x

unsafe {} literally means "shut up compiler I know what I'm doing and this code doesn't have any UB". In this case you lied to the compiler, there's no guarantee that the compiler will gives you meaningful result back.

9 Likes

When you use unsafe, you're taking on the responsibility of upholding the language invariants and giving up the safety afforded by having the compiler prove them. If you don't uphold them, it's undefined behavior (UB).

You can run this snippet using Miri on the playground (under Tools) to see it's UB.

Incidentally, instead of transmute, you can use unsafe { &mut *x.as_mut_ptr() }.

2 Likes

When you dereference a raw pointer and turn it into a borrow, rust creates a new lifetime that's detached from any original constraints, so you're able to do anything with it, even without transmute. Even this would compile:

let y: &'static mut i32 = unsafe { &mut *x.as_mut_ptr() };

Even though it's clearly undefined behaviour if you actually do it.

1 Like