Raw pointers & unsafe
Jack Everett Fletcher January 24, 2022 [Learning] #rustCode heavy overview on raw pointers + a little coverage on unsafe
Raw Pointers ➡️
author's note
This blog post is mostly as an exercise for my own understanding. It's the result of reading The Book, a handful of other blog posts on unsafe rust, and Rust In Action. I recommend you read those materials in addition to this condensed post.
Enjoy!
Code 🧑💻
fn main() {
// let x be a String on the heap
let x = String::from("hello");
// let y be a reference to x
let y: &String = &x;
// let z be a raw pointer to the reference of x. We use casting here.
let z = &x as *const String;
// let a be a raw pointer to the reference of x.
// Here, we specify the type directly, so there's no need for casting.
let a: *const String = &x;
// note that because x isn't mutable, we'll get a compile time error if we try to create a
// mutable raw pointer...
//let b: *mut String = &x;
// ...However, there's nothing stopping us from casting a const raw pointer to a mutable raw
// pointer. As such, *const and *mut are really just lints, not law, and it's the programmer's
// responsibility to uphold their meaning.
let c: *const String = &x;
let b = c as *mut String;
// It's important to note that creating raw pointers is perfectly safe. It's even perfectly safe
// to create a raw pointer that points to a bullshit location in memory, no issues.
let bullshit = 0x012345usize; // random memory address
// create what appears to be a valid raw pointer to a memory address containing a String
let decietful_pointer = bullshit as *const String;
// Additionally, it's totally fine to print out the memory location of the data the pointer
// points to.
println!("{:?}", a);
// But to actually dereference the raw pointer and get our String? That requires an unsafe
// block. Regrettably, the symbol for dereferencing both references and raw pointers is the same
// for creating raw pointers. See
// https://stackoverflow.com/questions/8685514/why-is-the-dereference-operator-also-used-to-declare-a-pointer
unsafe {
println!("{}", *a);
}
// Note that unsafe isn't just some magical word that obliterates all the safety Rust provides!
// The below code refuses to compile. The actual String is owned by x. Having the variable d
// own the String requires a clone, because the String type is non-trivial and therefore doesn't
// implement copy. Simply using = is not possible.
/*unsafe {
let d: String = *a;
}*/
// We have two options. We can either...
// ...dereference the raw pointer, let .clone() consume a reference to the data, and own the
// resulting cloned data.
let f: String;
unsafe {
f = (*a).clone();
}
// or use std::ptr::read(). read() is extremely powerful, but it's very easy to misuse.
// A small bit from the old documentation on read(), shown below, is pretty pragmatic.
// "Beyond accepting a raw pointer, this is unsafe because it semantically moves the value out of src without preventing further usage of src.
// If T is not Copy, then care must be taken to ensure that the value at src is not used before the data is overwritten again (e.g. with write, write_bytes, or copy).
// Note that *src = foo counts as a use because it will attempt to drop the value previously at *src."
// The new documentation's example where they manually implement mem::swap is a good example of this.
// https://doc.rust-lang.org/std/ptr/fn.read.html
unsafe {
let g: String = std::ptr::read(a);
// at the end of this block, g is dropped. If we try and do anything with x or dereference
// any of the pointers we've created to x, we'll get undefined behavior (UB). With UB,
// getting segfaults or sigabrts is best case scenario. At worst, we'll never know something
// is wrong.
}
// note that when main ends, it'll try to free x. We'll get a sigabrt, because a double free
// occurs.
}