Example: References

fn main() {
    let mut boxed = Box::new(123);
    let a: *mut i32 = &mut *boxed as *mut i32;
    let b: *mut i32 = std::ptr::null_mut();

    println!("{:?}", *a);
    println!("{:?}", b.as_mut());
}

Confirm understanding of the syntax

  • Box<i32> type is a reference to an integer on the heap that is owned by the box.

  • *mut i32 type is a so-called raw pointer to an integer that the compiler does not know the ownership of. Programmers need to ensure the rules are enforced without assistance from the compiler.

    • Note: raw pointers do not provide ownership info to Rust. A pointer can be semantically owning the data, or semantically borrowing, but that information only exists in the programmer’s mind.
  • &mut *boxed as *mut _ expression:

    • *boxed is …
    • &mut *boxed is …
    • finally, as *mut i32 casts the reference to a pointer.
  • References, such as &mut i32, “borrow” their referent. This is Rust’s ownership system.

Confirm understanding of ownership

  • Step through code:

    • (Line 3) Creates raw pointer to the 123 by dereferencing the box, creating a new reference and casting the new reference as a pointer.
    • (Line 4) Creates raw pointer with a NULL value
    • (Line 7) Converts the raw pointer to an Option with .as_mut()
  • Highlight that pointers are nullable in Rust (unlike references).

  • Compile to reveal the error messages.

  • Discuss

    • (Line 6) println!("{:?}", *a);
      • Prefix star dereferences a raw pointer.
      • It is an explicit operation. Whereas regular references have implicit dereferencing most of the time thanks to the Deref trait. This is referred to as “auto-deref”.
      • Dereferencing a raw pointer is an unsafe operation.
      • Requires an unsafe block.
    • (Line 7) println!("{:?}", b.as_mut());
      • as_mut() is an unsafe function.
      • Calling an unsafe function requires an unsafe block.
  • Demonstrate: Fix the code (add unsafe blocks) and compile again to show the working program.

  • Demonstrate: Replace as *mut i32 with as *mut _, show that it compiles.

    • We can partially omit the target type in the cast. The Rust compiler knows that the source of the cast is a &mut i32. This reference type can only be converted to one pointer type, *mut i32.
  • Add safety comments:

    • We said that the unsafe code marks the responsibility shift from the compiler to the programmer.
    • How do we convey that we thought about our unusual responsibilities while writing unsafe code? Safety comments.
    • Safety comments explain why unsafe code is correct.
    • Without a safety comment, unsafe code is not safe.
  • Discuss: Whether to use one large unsafe block or two smaller ones:

    • Possibility of using a single unsafe block rather than multiple.
    • Using more allows safety comments as specific as possible.

Suggested Solution

fn main() {
    let mut boxed = Box::new(123);
    let a: *mut i32 = &mut *boxed as *mut i32;
    let b: *mut i32 = std::ptr::null_mut();

    // SAFETY: `a` is a non-null pointer to i32, it is initialized and still
    // allocated.
    println!("{:?}", unsafe { *a });

    // SAFETY: `b` is a null pointer, which `as_mut()` converts to `None`.
    println!("{:?}", unsafe { b.as_mut() });
}