Skip to content

Stacked Borrows does not support &UnsafeCell pointing to read-only data #303

Open
@oconnor663

Description

@oconnor663

I'm playing with the idea of a ReadOnlyCell<T> type, like a Cell<T> that only supports reading. The (kind of academic) goal of this type is to allow conversions from either &T or &Cell<T> into &ReadOnlyCell<T>, similar to how Cell::from_mut converts from &mut T to &Cell<T>. Here's the example code (playground link):

use std::cell::{Cell, UnsafeCell};

#[repr(transparent)]
pub struct ReadOnlyCell<T>(UnsafeCell<T>);

impl<T> ReadOnlyCell<T> {
    pub fn new(val: T) -> Self {
        Self(UnsafeCell::new(val))
    }

    pub fn from_ref(t: &T) -> &Self {
        // SAFETY: &ReadOnlyCell<T> is strictly less capable than &T
        unsafe { &*(t as *const T as *const Self) }
    }

    pub fn from_cell_ref(t: &Cell<T>) -> &Self {
        // SAFETY: &ReadOnlyCell<T> is strictly less capable than &Cell<T>
        unsafe { &*(t as *const Cell<T> as *const Self) }
    }
}

impl<T: Copy> ReadOnlyCell<T> {
    pub fn get(&self) -> T {
        unsafe { *self.0.get() }
    }
}

fn main() {
    let my_int = &42;
    let read_only_cell = ReadOnlyCell::from_ref(my_int);
    assert_eq!(read_only_cell.get(), 42);
}

This fails Miri:

error: Undefined Behavior: trying to reborrow for SharedReadWrite at alloc45, but parent tag <untagged> does not have an appropriate item in the borrow stack
  --> src/main.rs:13:18
   |
13 |         unsafe { &*(t as *const T as *const Self) }
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trying to reborrow for SharedReadWrite at alloc45, but parent tag <untagged> does not have an appropriate item in the borrow stack
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
           
   = note: inside `ReadOnlyCell::<i32>::from_ref` at src/main.rs:13:18
note: inside `main` at src/main.rs:30:26
  --> src/main.rs:30:26
   |
30 |     let read_only_cell = ReadOnlyCell::from_ref(my_int);
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

It seems like Miri is telling me that converting &T to &UnsafeCell<T> is inherently UB. That's surprising to me. Obviously writing to that UnsafeCell would be UB. But here all I think I'm doing is telling the compiler not to treat the pointer as noalias, which is sort of like converting &T to *const T, which is legal. Am I thinking about this the right way?

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-aliasing-modelTopic: Related to the aliasing model (e.g. Stacked/Tree Borrows)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions