I believe it is safe to create a reference to the interior inside a method on Cell, because at that time there are no other references (Cell doesn't hand out references), and there are no other methods active at the same time.
This is safely available as Cell::get_mut: if you have exclusive access to the (Unsafe)Cell, it is sound to provide an internal reference.
Doing this with a shared reference is unsound.
The underlying desired operation (breaking &Cell<Struct> into &Cell<Field>s) is sound, but tricky.
Doing this with raw pointer manipulation (&raw or memoffset + ptr::offset) is the only way to do so without passing through bad internal references.
You can do this 100% soundly given #[repr(C)], or with memoffset. I'd say "just" use memoffset, as it's currently "UB but not exploited", and will be updated to be not fully safe and sound as soon as that becomes possible, and contains safeguards against misuse.
We are probably talking about a different thing. Inside a method on Cell, as an implementation detail, it should be safe to use a normal reference. Cell::replace actually does this.
Yeah, the key property is that between the time the shared reference is created and the last time it is used, no mutation of the memory it points to happens. Stacked Borrows additionally requires that shared references passed as function arguments also don't have their pointee mutated until the respective function returns. But a temporary shared reference to interior mutable data that is not actually currently being mutated is fine.
The key point I forgot and overlooked: Cell: !Sync, so only one location in the code is able to work on it at a time. This means Cell<T>::with_ref(impl for<'a> FnOnce(&'a T) -> R) -> R would be sound.
I feel like we should have a collection of unsound ideas somewhere, or else the same proposals are going to come up again and again. Though this time there is a funny timely coincidence where the proposals happened basically at the same time... or, is it really coincidence?^^
To do a shameless plug: the comment from @rpjohnst started as a reply on my blog post collecting extensions to interior mutability types (which was again inspired by a comment from @rpjohnst ), also listing why some are unsound. Maybe a starting point? I am currently interested in collecting a little more.
Could Cell projection be done as a macro, like this? Also your dioptre looks a lot like my RFC from a while back, and I made an unpublished crate to go along with the RFC
Yep! (Though I believe your &mut is unsound.) It's simple enough that I would probably just do that directly in a lot of scenarios, without pulling in a crate.
I published dioptre more because I have some use cases for the crate as a whole, and as a place to point people as an explanation.
That doesn't follow. That's like saying that Cell::set is unsound because it creates a &mut T while setting the value. There is nothing about creating this &mut T is wrong. Because for the entire lifetime of the &mut T there is no other way to access the pointee (T) other than going through the &mut T. Also the a new Cell is immediately created from the &mut T, so it doesn't leak to the outside world.
This does pass MIRI (although that isn't proof, it's still strong evidence because this is what MIRI was designed to catch).
Now this is UB, because if Field: Freeze, then &Field is immutable. But &Cell<Field> is most certainly not immutable. You must use Cell::from_mut or pointer casts from *mut T to *const Cell<T> to soundly get a Cell.
Also, there is a cost to using function pointers, so I'm not sure if that's the best way to do this. You can look into my unpublished crate, where I made a trait for field which handles the projection an lots of type-level meta programming to encode multiple fields and get the rest of the features. Though that may be a bit too heavy weight and hard to maintain.
There @RalfJung is talking about shared references into the Cell<T>, i.e. &T not &Cell<T>. In my example, the only things that could alias the &mut T are &Cell<T>. But given that the &mut T was derived from the &Cell<T> this is fine. It's the same as reborrowing. As long as I don't interleave accesses from &Cell<T> and &mut T I'm fine (and I don't interleave accesses).