Skip to content

Idea for disabling interior mutability that plays nicely with Stacked Borrows #1760

Open
@joshlf

Description

@joshlf

See it on the Rust Playground

use core::cell::UnsafeCell;
use zerocopy::AsBytes;

// INVARIANT: A pointer to `Self` is never created with `SharedReadWrite`
// permissions.
#[repr(transparent)]
pub struct ReadOnly<T>(T);

impl<T> ReadOnly<T> {
    fn new(t: T) -> ReadOnly<T> {
        ReadOnly(t)
    }
}

impl<T> ReadOnly<T> {
    fn from_mut<'a>(t: &'a mut T) -> &'a mut ReadOnly<T> {
        let t_ptr: *mut T = t;
        // INVARIANT: Because `t: &mut T`, `t_ptr` has `Unique` permissions, not
        // `SharedReadWrite` permissions. This is true even if `T` contains
        // `UnsafeCell`s.
        unsafe { &mut *t_ptr.cast::<ReadOnly<T>>() }
    }
}

impl<T: AsBytes> ReadOnly<UnsafeCell<T>> {
    fn as_bytes(&self) -> &[u8] {
        let len = size_of_val(self);
        let slf: *const Self = self;
        // SAFETY: Since, by invariant, a pointer to `Self` is never constructed
        // with `SharedReadWrite` permissions, we know that `self` must only
        // have `Shared` permissions. Thus, this operation does not invalidly
        // re-tag the pointer's permissions.
        //
        // Since `T: AsBytes` and since `UnsafeCell<T>` has the same bit
        // validity as `T`, none of the bytes of `*self` are uninitialized.
        unsafe { core::slice::from_raw_parts(slf.cast::<u8>(), len) }
    }
}

fn main() {
    // By value
    let ro = ReadOnly::new(UnsafeCell::new(0u8));
    println!("{:?}", ro.as_bytes());

    // By reference
    let mut val = UnsafeCell::new(0u8);
    let ro = ReadOnly::from_mut(&mut val);
    println!("{:?}", ro.as_bytes());
}

The key idea is that, while Stacked Borrows can't be used to disable UnsafeCells if you've started out with a &UnsafeCell, which has SharedReadWrite permissions (see rust-lang/unsafe-code-guidelines#303), it's still sound so long as you never create a pointer with SharedReadWrite permissions in the first place. You can do that by only constructing a ReadOnly by value or by &mut.

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