Open
Description
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?