-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UnsafePinned: allow aliasing of pinned mutable references #3467
Conversation
018a865
to
51bed0f
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
Strongly in favor. But bikeshed time:
This wording initially threw me for a loop, given that it's much more common to see That said, I still think |
1bb9b95
to
d599c1e
Compare
Hm, yeah... however the aliasing and dereferenceable rules are still in full effect on I tried to clarify this in the RFC text. |
So, I definitely feel like And when I think over and over again about what that kind of API would look like, I keep coming back to the same conclusion: isn't that just Like, the confusion, I think, is the fact that "aliased" implies that the issue is someone else having a reference to the value, when the real issue is that the value itself cannot be referenced safely. So, in the first example given, the issue isn't that we've got the reference I tried a bit to sketch this safe I had a rough time properly formulating this since, although noalias is a simple optimisation, the actual way it breaks the borrow mechanics are hard to describe. I probably did a terrible job, but maybe it helps improve the way this is explained. |
A safe counterpart would be some I also think
I don't understand the distinction you are making between a reference existing to the value vs the value being referenced.
So... |
Co-authored-by: Jacob Lifshay <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(top level comment is just naming bikeshed)
The closest to a safe equivalent to UnsafeAliased<T>
would be Pin<Box<T>>
. There cannot be a full safe equivalent, though; to a rough estimation, the purpose of UnsafeAliased
is to make &mut UnsafeAliased<T>
act like &UnsafeCell<T>
(w.r.t. the borrow opsem).
This lens does offer an alternative avenue for deriving a name — focusing on that w.r.t. aliasing, UnsafeAliased
behaves as-if it introduces an indirection. Except that this still isn't quite accurate, since adding a (raw) pointer indirection also enables shared mutability but UnsafeAliased
doesn't.
Given that UnsafeAliased
is to &mut T
as UnsafeCell
is to &T
, I don't actually dislike UnsafeMut
as a name any more than I consider UnsafeCell
an imperfect1 name. UnsafeCell
does have "this is a cell type but unchecked" as an analogy driving its name, and UnsafeAliased
doesn't have any such "but unchecked" analogy to directly lean on, but it can still pull the analogy to cell types' impact on &Cell
and apply that to &mut Cell
.
Arguing this naming direction becomes easier if UnsafeMutCell
also relaxes &
, i.e. is a standard cell type (is !Freeze
) in addition. Usage which can be made sound without UnsafeCell
semantics are quite rare2, but they do exist, and pending decisions around cell preciseness, the memory regions with the separate relaxations may not always subset nicely.
The other potential name which I'm partial to is UnsafePinCell
. This leans more into naming based on usage instead of effect, as well as leaning on PhantomPinned
(which could be potentially defined as UnsafePinCell<()>
). This becomes more interesting of a naming direction if a) we commit to infectious cell semantics and b) provide a PhantomMut
as UnsafeCell<()>
/ Freeze
optout.
Footnotes
-
UnsafeCell
is an imperfect name because it's not just an uncheckedCell
. std nonexhaustively lists all ofCell
,RefCell
,OnceCell
,Mutex
,RwLock
,OnceLock
, and the atomic types as cell types, andUnsafeCell
is more accurately described as unchecked any one of those. ↩ -
Shared cell semantics are needed because getting
&S
fromPin<&mut S>
, and this attempts to retagS
's bytes as ref-borrowed, which would invalidate any previously derived mut-borrowed provenance, IIUC. As such, any usage withoutUnsafeCell
would need to only dynamically extend the static shared-provenance and not mut-provenance. ↩
text/0000-unsafe-aliased.md
Outdated
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
API sketch: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should UnsafeAliased
be !Unpin
? This (negative) impl listed isn't in the sketch, but it must be for the earlier guide-level line about Pin<&mut S>
soundness to be accurate.
By analogy to how the other autotraits are treated, I argue that yes, it should be not-Unpin
. The user can always implement Unpin
if desired.
text/0000-unsafe-aliased.md
Outdated
|
||
- We have a `Unique` auto trait similar to `Freeze` that is implemented if the type does not contain any by-val `UnsafeAliased`. | ||
- `noalias` on mutable references is only emitted for `Unique` types. (This replaces the current hack where it is only emitted for `Unpin` types.) | ||
- Miri will do `SharedReadWrite` retagging inside `UnsafeAliased` similar to what it does inside `UnsafeCell` already. (This replaces the current `Unpin`-based hack.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's clear that &UnsafeAliased<T>
carries a Shared
tag, and that mutating through it is UB (assuming no UnsafeCell
is present). What's not clear is how retagging impacts extant sibling write-capable provenance.
This very much gets into some undecided specifics to specify precisely, but some specifics are needed. Namely, if I do
let mut s = pin!(S::new());
s.as_mut().get_data(); // init ptr
black_box(s.as_ref()); // retag &S
s.as_mut().set_data(42); // write through ptr
is this guaranteed to be permitted? If it is, then IIUC, &UnsafeAliased<T>
can protect the bytes such that writing to them would be UB, but must not retag them in such a way as to completely invalidate sibling write-capable provenance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea where existing siblings matter in your example.
In SB, each as_mut
creates a new SRW child of s
. Each as_ref
creates a new SRO child. Both of these actions preserve existing SRW and SRO children of s
.
In TB, as_mut
will just return the original provenance -- no retagging for !Unique
mutable references. as_ref
will create a read-only child that lives as long as there is no write.
Your example doesn't even exploit any of that though. It would be allowed even if creating a new SRW or SRO child first killed all previously created children, since you are creating new pointer with calls to as_mut
/as_ref
all the time. Did you mean to keep the first as_mut
pointer around and use it again after the as_ref
?
I mean, even such construction would be incomplete, TBQH. In the second example given, for example,
I sympathise with the concept of "cell" being a horrible name, although what I like about it is that it has actually two names: In terms of the "second" name here, what we're offering is effectively an alternative to &own references or &move references via an unsafe API. What we're doing is effectively an owning reference, since simple self-referentiality can be accomplished without the need for these unsafe-aliasing features.
In terms of ownership,
One other term I thought of was "disowned" since |
That's wrong. I am not aware of a single usage that needs |
|
And even if it was a reference, this example would be fine -- the lifetime of the reference ends after the let mut data = 0;
let ptr_to_data = &mut data;
yield;
*ptr_to_data = 42;
println!("{}", data);
return;
This is trivially true in the sense that if everything is read-only then there are no aliasing violations. Self-referential webs of shared references can be created in safe code. But for better or worse, we need self-referential data with mutable references, e.g. to support the Maybe you want to say that
Insofar as "interior mutability" is a (terrible) name for "mutability behind shared references", the corresponding thing for
Nobody is creating a mutable reference to
It would be You can't really go with any kind of ownership story for this RFC since the ownership of |
I think we should avoid "cell", but I like Without infectious cell semantics, switching Miri over to So yeah I really like the idea of re-defining |
SO something I don't like about this RFC is that it requires tracking the provenance of a reference through all code you control, in case that reference somehow aliases. My guess is that it's only a matter of time until someone uses this wrong internally, but in a important crate like Unfortunately I absolutely don't have a better suggestion here. I'd offer one if I could. The drawbacks section does acknowledge this, but the core crate concern makes me flinch a bit. I don't want to be the one who finds miscompilation because of something that's a 5-times dependency and we decided to upgrade Rust. I'm not sure how we'd even track that back to the problem. Unfortunately something like this is probably necessary. I just don't like what appears to me to be a major incident waiting to happen. Imagine if this took out tokio. |
It's interesting that you mention Tokio. I'm one of the maintainers of Tokio. Tokio does have unsafe code that relies on the so-called Unpin hack to write custom self-referential futures (well, mostly futures with intrusive linked lists). In fact, I suspect that Tokio is the single largest user of the Unpin hack on crates.io. We justify this because it lets us eliminate a bunch of allocations, and I have been monitoring the state of these issues for a very long time, and I have always been ready to replace the Unpin hack with a proper solution once one is stabilized. For example, see this gist that I wrote back in Jan 2021 advocating for pretty much the solution proposed here. |
@rfcbot fcp merge We discussed this RFC in today's lang team meeting (notes) and I think we should move forward with the RFC. TeachingOne concern I had is around how to teach proper use of This is somewhat different from @RalfJung used a framing of "capabilities" that I thought was insightful. Generic code which has NamingI raised an eyebrow at the name
The fact that the use of That said, @joshtriplett also raised concerns about the name and I believe he would like to have it included as an open question in the RFC. |
Team member @tmandry has proposed to merge this. The next step is review by the rest of the tagged team members:
No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
For clarity, no objection to the introduction of this in nightly. I'd like to add an unresolved question about naming that we resolve before stabilization, but that's not a blocker for experimentation in nightly. |
I added the name to the Unresolved questions. |
@rfcbot reviewed |
The name |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
The teams have accepted this RFC, and we've now merged it. Thanks to @RalfJung for pushing this high quality work forward, and thanks to those who reviewed this work and provided helpful feedback. For further updates, follow the tracking issue: |
Add a type
UnsafePinned
that indicates to the compiler that this field is "pinned" and there might be pointers elsewhere that point to the same memory.This means, in particular, that
&mut UnsafePinned
is not necessarily a unique pointer, and thus the compiler cannot just make aliasing assumptions.However,
&mut UnsafePinned
can still bemem::swap
ed, so this is not a free ticket for arbitrary aliasing of mutable references.You need to use mechanisms such as
Pin
to ensure that mutable references cannot be used in incorrect ways by clients.This type is then used in generator lowering, finally fixing #63818.
Thanks to @rust-lang/opsem for a bunch of useful feedback!
Rendered
Tracking: