Description
Our example for dereferencing raw pointers uses a raw pointer to a String:
fn main() {
let mut s = String::from("careful!");
let r1 = &raw mut s;
let r2 = r1 as *const String;
// SAFETY: r1 and r2 were obtained from references and so are guaranteed to
// be non-null and properly aligned, the objects underlying the references
// from which they were obtained are live throughout the whole unsafe
// block, and they are not accessed either through the references or
// concurrently through any other pointers.
unsafe {
println!("r1 is: {}", *r1);
*r1 = String::from("uhoh");
println!("r2 is: {}", *r2);
}
// NOT SAFE. DO NOT DO THIS.
/*
let r3: &String = unsafe { &*r1 };
drop(s);
println!("r3 is: {}", *r3);
*/
}
It then creates a *const String
and a *mut String
, mutates the String through the *mut String
, and observes that mutation through the *const String
. This suffices to show how raw pointers do not obey the same rules as references: mutable and constant ones can coexist, and we can observe mutation through them.
But when thinking about this in terms of references, it's strange to see a String
accessed indirectly in this way. Rust programs don't often construct &String
except as a temporary, and &mut String
is similarly uncommon except as receiver of methods. When we think of *const String
and *mut String
, these are essentially pointers to a 3-field struct, and the assignment to *r1
overwrites this struct. In doing so, it destroys the previous value, an object with a heap allocation and associated Drop
impl. The Drop
impl is still used when we drop the old value, but it is another thing to be aware of here, and it might be better to introduce raw pointers in a situation where ownership is not involved.
Something like this might be clearer:
fn main() {
let mut s = Box::new(5);
let r1: *mut i32 = &raw mut *s;
let r2: *const i32 = r1 as *const i32;
// SAFETY: r1 and r2 were obtained from a box and so are guaranteed to
// be non-null and properly aligned, the objects underlying the references
// from which they were obtained are live throughout the whole unsafe
// block, and they are not accessed either through the box or
// concurrently through any other pointers.
unsafe {
println!("r1 is: {}", *r1);
*r1 = 100;
println!("r2 is: {}", *r2);
}
// NOT SAFE. DO NOT DO THIS.
/*
let r3: &i32 = unsafe { &*r1 };
drop(s);
println!("r3 is: {:?}", *r3);
*/
}