I was writing a toy Rc by dereferencing an &T and unsafely mutating the counter, like so:
impl<T> MyRC<T>
where
T: 'static
{
pub fn clone_rc(&self) -> Self {
unsafe {(*self.inner).count += 1;}
Self {inner: self.inner}
}
}
// and yes, I probably don't need the T: 'static bound
On reading UnsafeCell's documentation, it seems to state that breaking an &T guarantees without the use of UnsafeCell is undefined behavior.
However, I ran cargo +nightly miri run with this main.rs:
struct Noisy(u32);
impl Drop for Noisy {
fn drop(&mut self) {
println!("Dropping!");
}
}
fn main () {
let n = MyRC::new(Noisy(3));
{
let _ = n.clone_rc();
}
drop(n)
}
And there wasn't any output from Miri.
So is this even undefined behaviour? If this is, how would I find out?
It seems like self.inner is a raw pointer. The immutability guarantee doesn't apply "through" pointers. The raw pointer itself determines whether you can modify what it points at, and not whether you used a &T or &mut T to get to the raw pointer itself.
Mutating that data, for example through an alias or by transmuting an &T into an &mut T, is considered undefined behavior. (from the UnsafeCell documentation)
But what you're saying is that &T as *mut T is fine? Particularly in the case that I'm storing and writing to it as a *mut T, not converting an &T to *mut T at time of write?
You can safely convert a &T to a *const T, then convert the *const T to a *mut T. However, it is undefined behavior to actually write to that *mut T, or to convert it to a &mut T.
What @alice is saying is that you can dereference a &*mut T (i.e., an immutable reference to a mutable raw pointer) to obtain a *mut T, then write to that *mut T, assuming that you are otherwise allowed to write to it. This also applies to &MyStruct, where MyStruct contains *mut T.