Files
Sprimo/skills/unsafe-checker/rules/ptr-06-prefer-cast.md
2026-02-12 22:58:33 +08:00

3.0 KiB

id, original_id, level, impact, clippy
id original_id level impact clippy
ptr-06 G.UNS.PTR.03 G LOW ptr_as_ptr

Prefer pointer::cast Over as for Pointer Casting

Summary

Use the cast() method instead of as for pointer type conversions. It's clearer and prevents accidental provenance loss.

Rationale

  • cast() only changes the pointed-to type, not pointer properties
  • as can accidentally convert to integer and back, losing provenance
  • cast() is more explicit about intent
  • Better tooling support (clippy, miri)

Bad Example

// DON'T: Use `as` for pointer casts
fn bad_cast(ptr: *const u8) -> *const i32 {
    ptr as *const i32  // Works, but less clear
}

// DON'T: Accidental provenance loss
fn bad_roundtrip(ptr: *const u8) -> *const u8 {
    let addr = ptr as usize;   // Converts to integer
    addr as *const u8          // Loses provenance information!
}

// DON'T: Multiple `as` casts in chain
fn bad_chain(ptr: *const u8) -> *mut i32 {
    ptr as *mut u8 as *mut i32  // Hard to follow
}

Good Example

// DO: Use cast() for pointer type changes
fn good_cast(ptr: *const u8) -> *const i32 {
    ptr.cast::<i32>()
}

// DO: Use cast_mut() for const-to-mut (when valid)
fn good_cast_mut(ptr: *const u8) -> *mut u8 {
    ptr.cast_mut()  // Only use when mutation is valid!
}

// DO: Use cast_const() for mut-to-const
fn good_cast_const(ptr: *mut u8) -> *const u8 {
    ptr.cast_const()
}

// DO: Chain casts clearly
fn good_chain(ptr: *const u8) -> *mut i32 {
    ptr.cast_mut().cast::<i32>()
}

// DO: Use with_addr() for address manipulation (nightly)
#[cfg(feature = "strict_provenance")]
fn good_provenance(ptr: *const u8, new_addr: usize) -> *const u8 {
    ptr.with_addr(new_addr)  // Preserves provenance
}

Pointer Method Reference

Method From To Notes
.cast::<U>() *T *U Changes pointee type
.cast_mut() *const T *mut T Removes const
.cast_const() *mut T *const T Adds const
.addr() *T usize Gets address (nightly)
.with_addr(usize) *T *T Changes address, keeps provenance
.map_addr(fn) *T *T Transforms address

Provenance Considerations

// Provenance = permission to access memory

// BAD: Loses provenance
let ptr: *const u8 = &data as *const u8;
let addr = ptr as usize;
let ptr2 = addr as *const u8;  // ptr2 has no provenance!

// GOOD: Preserves provenance (nightly strict_provenance)
let ptr2 = ptr.with_addr(addr);  // Still has permission

// GOOD: Use expose/from_exposed when provenance must cross integer
let addr = ptr.expose_addr();  // "Expose" the provenance
let ptr2 = std::ptr::from_exposed_addr(addr);  // Recover it

Checklist

  • Am I using as where cast() would be clearer?
  • Am I accidentally converting through usize?
  • Do I need to preserve provenance?
  • ptr-04: Alignment considerations when casting
  • ptr-05: Don't convert const to mut improperly