3 ms·
One of the strange things about Rust is the &UnsafeCell<T>/*mut T dichotomy. &UnsafeCell<T> is easier to work with, and you can soundly acquire &mut T as long a
by nyanpasu64 5y ago
One of the strange things about Rust is the &UnsafeCell<T>/*mut T dichotomy. &UnsafeCell<T> is easier to work with, and you can soundly acquire &mut T as long as they never overlap, but you can't turn a Box<UnsafeCell<T>> into a &UnsafeCell<T> and back to a Box<UnsafeCell<T>> to delete it, because provenance or something.
*mut T is harder to work with, this is UB according to miri since you didn't specify `&mut x as *mut i32 as *const i32`:
let mut x = 1;
let px = &mut x as *const i32;
unsafe {
*(px as *mut i32) = 2;
}
Problem is, most APIs won't give you a &UnsafeCell<T> but rather a &mut T. Not sure if you can convert a &mut T to a &UnsafeCell<T> (you definitely can't using `as`). If you want to create multiple aliasing pointers into a non-UnsafeCell type or struct field, one approach (basically a placeholder since &raw isn't stable, https://gankra.github.io/blah/fix-rust-pointers/#offsets-and-places-are-a-mess https://gankra.github.io/blah/fix-rust-pointers/#offsets-and...) is:
let mut x = 1;
let px = addr_of_mut!(x);
unsafe {
*px = 2;
}
- kzrdude 5y agoCell::from_mut converts from `&mut T` to `&Cell<T>` and that's a newtype around UnsafeCell - it has the same `as_ptr` method.
- zozbot234 5y agoThese are different things. UnsafeCell<T> is for shared mutable data. *mut T is for data that you assert will never be mutated while it's being shared with some other reference, but you can't rely on the compiler to prove this fact for you.
- nyanpasu64 5y agoIf I have a &mut T, what pointer type do I convert it into (and create multiple copies of), to allow shared mutation scoped within the lifetime of the source &mut T?
- zozbot234 5y agoYou can have multiple *mut T aliasing to the same data, but if you mutate through them you're on your own wrt. Rust's safety rules.
- yakubin 5y agoYou cannot turn a &T into a Box<T>, because &T borrows T, while Box<T> owns T, and moreover it holds it in a separate allocation, so even &mut T cannot be transformed into Box<T> --- it already lives in some allocated space and whatever there is a reference to, cannot be moved to a new allocation. For moving T you need T, not a reference to T. The case with UnsafeCell<T> substituted in place of T is just a special case. UnsafeCell<T> also owns T, so transforming &mut T into UnsafeCell<T> also doesn't make sense. The unsafe equivalent of references is pointers.
- nyanpasu64 5y agoC++ lets you easily delete a T* or T const*, Rust has https://doc.rust-lang.org/std/primitive.pointer.html#2-consume-a-box-boxt https://doc.rust-lang.org/std/primitive.pointer.html#2-consu... I guess? > UnsafeCell<T> also owns T, so transforming &mut T into UnsafeCell<T> also doesn't make sense. I wanted to transform a &mut T into &UnsafeCell<T> (note the &) and copy the reference, to allow shared mutation scoped within the lifetime of the source &mut T. How can this be accomplished?
- yakubin 5y ago> C++ lets you easily delete a T* or T const*, Rust has https://doc.rust-lang.org/std/primitive.pointer.html#2-consu https://doc.rust-lang.org/std/primitive.pointer.html#2-consu... I guess? Box deletes the owned object when it goes out of scope without being moved (like unique_ptr in C++). So if anything, you want to go the other way: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.from_raw https://doc.rust-lang.org/std/boxed/struct.Box.html#method.f... However, you can delete a *T by using <https://doc.rust-lang.org/std/alloc/trait.Allocator.html#tymethod.deallocate https://doc.rust-lang.org/std/alloc/trait.Allocator.html#tym...> with the Global allocator (since this is the one you're most likely using). > I wanted to transform a &mut T into &UnsafeCell<T> (note the &) and copy the reference, to allow shared mutation scoped within the lifetime of the source &mut T. How can this be accomplished? If you want to have two instances of one &mut T, you don't go through &UnsafeCell<T>. Instead you may cast &mut T into *mut T and then use this: <https://doc.rust-lang.org/std/primitive.pointer.html#method.as_mut-1 https://doc.rust-lang.org/std/primitive.pointer.html#method....>. This however will cast into any lifetime, so if you want to bind the two lifetimes together, then you need to have the lifetime of the original &mut T explicitly specified, and then you assign the result of the method I linked to a variable with explicitly specified type where you specify the lifetime annotation. Alternatively, you may write a separate function which accepts both references as arguments and binds the two lifetimes together the usual way. I admit it's a bit unergonomic. The best way currently would be to have the data stored as UnsafeCell in the first place and then call get_mut() on it to get all the references. However, if this reference comes from outside, you're left with the little mess I outlined above.