👨‍💻blog + programming

Raw pointers & unsafe

Jack Everett Fletcher January 24, 2022 [Learning] #rust

Code heavy overview on raw pointers + a little coverage on unsafe

memory layout

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.
}

Back to top