Skip to content

Raw pointers example using String is confusing #2734

Open
@fw-immunant

Description

@fw-immunant

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);
    */
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions