One way to allow self-referential structs would be a new type UnsafeAlias, which allows a pointer and a mutable reference to overlap. The proposed api would be:
impl<T:?Sized> UnsafeAlias<T>{
pub fn new(value:T) where T:Sized ->Self
pub fn as_ref(&self)-&T
pub fn from_ref(reference:&T)->&Self
pub fn from_mut(reference:&mut T)->&mut Self
pub fn into_inner(self)->T where T:Sized
///Safety:The returned pointer is safe to read from but can cause undefined behaviour
///if written to(unless the data written to is inside an `UnsafeCell`)
pub fn get_ptr(&mut self)->*const T
}
impl<T:?Sized> !Unpin for UnsafeAlias<T>{}
This would allow self-referential structs and async functions while still allowing optimisations on shared references. On an llvm level, the only change would be removing the noalias annotation on mutable references pointing to a type containing a UnsafeAlias ;Shared references can remain noalias readonly(assuming the type does not contain a UnsafeCell).
The only change to Tree Borrows that would be needed to accommodate this would be adding a check to the transition from active to a frozen and leaving the reference in the active state if the affected byte is in an UnsafeAlias.
Would it make legal (that is, non-UB) to actually perform read and writes from both &mut UnsafeAlias<T> and *mut UnsafeAlias<T> or this only prevents the insta UB that happens when creating the alias?
You're proposing this in addition to Pin and Unpin, to replace the rules currently used for noalias, without changing the library-soundness aspects of Pin and Unpin? Presumably generated async function bodies would be wrapped in this (or treated as if they were by the compiler)?
Would this have a negative impl of Unpin? it seems like it should.
And because of this, anyone currently relying on the pin guarantees (like tokio) would now be unsound, right? They would need to wrap their types in this? So it would be a technically breaking change for those users.
It would be legal to read, but not write, to the derived pointer(with the normal exception for UnsafeCell); The fact that get_ptr() returns a *const instead of a *mut is supposed to imply this.
I will add that.
The easiest way to phase this in would be to say that it is technically Undefined Behaviour, but not to optimise around it until the next edition (Technically &mut !Unpin should have noalias applied now, so tokio(and other crates that rely on this behaviour) are already unsound).
hmm... we guess UnsafeAlias is like some sort of AlwaysShared...
so &mut T to &mut AlwaysShared<T> is like &mut T to &T?
the problem is once you create the self-references...
hmm so maybe it's not inherently unsound but you'd need to undo the self-references by the time you go back to having a &mut T. which sounds like a footgun.
(we still think we'd be better off getting selfref into std...)
This sounds very similar to my own prior proposal. Would be good to see a comparison.
Pin guarantees in general are unaffected. This is about self-referential generators, which none of the Pin RFCs ever specified how to define.
What would be unsound is defining your own self-referential generators. We never stably guaranteed that you could even do that; the only stable way we expose is to write an async fn and those will be fixed by adding UnsafeCellMut/UnsafeAlias to their type in a suitable manner.
So, we might be able to justify this de jure. But more relevantly: how big is this problem? How many manual implementations of self-referential futures (not using async fn) are there out there that would be rendered unsound by this?
Oh, I think I see what you mean. Whether such code is rendered unsound heavily depends on the details of the code and the aliasing model (Stacked Borrows vs Tree Borrows).
I think the best solution would be to still leave the current !Unpin hack in place, but mark it as depreciated/don't document it at all. This means all existing code still works, but at some point in the future, we can remove the hack.