Closed withoutboats closed 5 years ago
I agree that finding better names may be a worthwhile bikeshedding exercise for the purposes of making pinning easier to talk about. I propose:
Pin
-> Pinned
: When you're given a Pin
, that's really a promise that what you're given has been pinned, and will remain pinned forever. I don't feel too strongly about this though, as you could also talk about being given a "pin of a value". Pinned<Box<T>>
just reads nicer to me, especially in that only the Box
is pinned, not the stuff it contains.Unpin
-> Repin
: for other marker traits, we usually talk about what you can do with something that has that marker trait. Which is presumably why Unpin
was chosen in the first place. However, I think what we really want the reader to take away here is that something that is Unpin
can be pinned, and then re-pinned somewhere else, without consequence or consideration. I also like @mark-i-m's suggestion of PinNeutral
, even though it's somewhat more verbose.Pinned
-> PermanentPin
: I don't think Pinned
is a good name, because something that contains a Pinned
isn't really pinned.. It just isn't Unpin
. PhantomPin
has a similar issue in that it refers to Pin
, when Pin
isn't really the thing you want to get at. NotUnpin
has a double negative, which makes it hard to reason about. @Kimundi's sugestion of PhantomSelfRef
gets pretty close, though I still think it's a little "complex", and it ties the property "cannot be moved once pinned" with one instance where that is the case (when you have a self reference). My suggestion could also be phrased as PermanentlyPinned
; I don't know which form is less bad.I think that Pinned
should end up being NotX
where X
is whatever Unpin
ends up being named. The only job of Pinned
is to make it so the enclosing type does not implement Unpin
. (Edit: and if we're changing Unpin
to something else, presumably the double negative isn't an issue)
Repin
doesn't make sense to me, because "This type can be pinned somewhere else" is just a side effect of "This type can be moved out of a pin".
@tikue In some sense I feel the same, but in reverse. I think Unpin
should be phrased in the negative "this is not constrained by Pin
", whereas Pinned
should be phrased in the positive "this is constrained by Pin
". Mostly just to avoid the double negative. But that's a detail; I do like the idea of there being a duality. Maybe: s/Unpin/TemporaryPin
, s/Pinned/PermanentPin
?
EDIT: Yeah, I see your point about Repin
being a side-effect of Unpin
. I wanted to communicate the fact that the Pin
is "unimportant" for a type that is Unpin
, which I don't think Unpin
does super well. Hence the suggestion above of TemporaryPin
.
@jonhoo I think the main reason I prefer the opposite is because Pinned
prevents a trait from being implemented, so that is, in the plainest sense to me, the true negative.
Edit: what about:
Unpin -> Escape
Pinned -> NoEscape
Interesting.. I'm trying to see how it'd fit into the documentation. Something like:
In general, when you are given a
Pin<P>
, that comes along with a guarantee thatP
's target will not move until it is dropped. The exception to this is ifP
's target isEscape
. Types marked asEscape
promise that they remain valid even if they are moved (for example, they contain no internal self-references), and they are therefore allowed to "escape" aPin
.Escape
is an auto-trait, so all types that consist entirely of types that areEscape
are themselves alsoEscape
. Implementors can opt out of this on nightly usingimpl !Escape for T {}
, or by including theNoEscape
marker fromstd::phantom
.
That seems pretty decent, though the connection to the word "escape" seems a little tenuous. Separately, writing the above also made me realize that Pin<P>
doesn't really guarantee that P
's target won't be moved (precisely because of Unpin
). Instead, it guarantees that either it doesn't matter if P
's target moves or P
's target won't be moved. Don't know how to use that to inform a better choice of names though... But it is probably something that should make it into the docs one way or another.
Personally, I too have a big dislike for Unpin
as a name, maybe because it is most often seen as impl !Unpin
which reads "not-un-pin" and requires several brain-cycles (I'm an older model) to conclude that it means "okay this one will be pinned forever once it is pinned the first time", so I can't even optimize away the double negative.
In general, humans tend to have a harder time thinking in negatives instead of positives (without a direct source, but check out the work of Richard Hudson if in doubt).
Btw Repin
sounds really nice to me.
Pin
is hard to explain because it doesn't always make the pinned value immovable. Pinned
is confusing because it doesn't actually pin anything. It just prevents escaping a Pin
.
Pin<P<T>>
could be explained as a pinned value: pinned values can't move unless the value is a type that can escape a pin.
Some quick googling seems to show that in wrestling, when one is pinned, breaking out of a pin is called escaping.
I also like the term escape instead of Unpin
but I would go for EscapePin
.
Lots of good thoughts here!
One general thing: For me as a non-native speaker Pin
and Unpin
are mostly verbs/actions. While Pin
makes sense, since the object is pinned to one memory location at a time, I can't see the same for Unpin
. Once I receive a reference of Pin<&mut T>
, T will always be pinned in the sense that it's memory location is stable, whether it's Unpin
or not. It's not possible to really unpin an object as an action. The difference is that Unpin
types do not require the guarantees of pinning to be uphold in further interactions. They are not self-referential, and their memory address is e.g. not sent to another object and stored there.
I would agree with @tikue that it would be nice to have a striking work for what Unpin
actually means, but it's hard to quantify. It's not purely that those types are moveable, and it's also not purely their lack of self-referentiality? Maybe it's something about "no pointers in the whole memory space get invalidated when the object is moved". Then something like StableOnMoveAfterPin
, or just StableMove
might be an option, but those also sounds not really great.
Repin
for me has the same complications as Unpin
, which is that it implies that one thing first gets unpinned - which in my understanding does not happen.
Since the trait mostly defines what happens after one sees a Pin
of the type, I find things like PinNeutral
, or PinInvariant
not too bad.
Regarding Pin
vs Pinned
, I think I would prefer Pinned
, since that's the state of the pointer at the point of time when one sees it.
@Matthias247 I don't think that you are guaranteed that P::Target
's address is stable if P: Unpin
? I could be wrong about this though?
@Matthias247
Once I receive a reference of Pin<&mut T>, T will always be pinned in the sense that it's memory location is stable, whether it's Unpin or not.
Can you clarify what you mean by this? Given a T: Unpin
you can write the following:
let pin_t: Pin<&mut T> = ...
let mut other_t: T = ...
mem::replace(Pin::get_mut(pin_t), &mut other_t);
// Now the value originally behind pin_t is in other_t
@jonhoo Actually a good question. My reasoning was that the futures get boxed, and then their poll()
method will be called on the same memory address. But that obviously only applies to the top level task/future, and the intermediate layers might may move futures around when they are Unpin
. So it seems like you are right.
W.r.t. the latest bikeshedding:
Unpin
: what about MoveFromPin
? If I'm not still missing some subtleties, I think this directly states what capability the trait actually enables: if the type is within a Pin
, you can still move it.
Crucially it kind of says the same thing as Unpin
, but framed as a positive assertion, so instead of the double-negative !Unpin
we now have !MoveFromPin
. I think I find that easier to interpret, at least... it's uh, types you can't move out of a pin.
(There's some room for variation on the basic idea: MoveOutOfPin
, MoveFromPinned
, MoveWhenPinned
, and so on.)
Pinned
: this can then become NoMoveFromPin
, and its effect is to make a type !MoveFromPin
. I think that seems straightforward enough.
Pin
itself: this one is unconnected to the other two and also not as significant, but I think there might be room for some slight improvement here as well.
The issue is that Pin<&mut T>
(for instance) doesn't mean that the &mut
is pinned, it means that the T
is (a confusion I think I've seen at least one recent comment evidence). Since the Pin
part acts as a kind of modifier on the &mut
, I think there's a case that it would be better to call it Pinning
.
There is some indirect precedent for this: if we want to modify the overflow semantics of an integer type to wrap around instead of panic, we say Wrapping<i32>
rather than Wrap<i32>
.
All of these are longer than the originals, but given how delicate some of the reasoning around these is, it might be a worthwhile investment for greater clarity.
Re: Repin
, I would expect this to be something like
unsafe trait Repin {
unsafe fn repin(from: *mut Self, to: *mut Self);
}
which could be used to support !Unpin
types inside a Vec
-like collection that occasionally moves its contents (this is not a proposal for adding such a trait now or ever, just my first impression from the trait name).
Also bikshedding the names:
Pin<P>
-> Pinned<P>
: the value the the pointer P
points to is pinned in memory for it's lifetime (until dropped).Unpin
-> Moveable
: the value doesn't need to be pinned and can be freely moved around.Pinned
(struct) -> Unmoveable
: the needs to be Pinned
and can't be moved around.I don't think Pin
or Unpin
should change, the alternatives all add verbosity without clarity in my opinion, or are even quite misleading. Also we had this conversation already, reached a decision to use Pin
and Unpin
, and none of the arguments brought up in this thread are new.
However, Pinned
was added since the previous discussion, and I think it makes sense to make it PhantomPinned
to be clear its a phantom marker type like PhantomData
.
Personally, I too have a big dislike for Unpin as a name, maybe because it is most often seen as impl !Unpin which reads "not-un-pin" and requires several brain-cycles (I'm an older model) to conclude that it means "okay this one will be pinned forever once it is pinned the first time", so I can't even optimize away the double negative.
This is completely the opposite of my experience, manually implementing !Unpin
means you're implementing a self-referential structure by hand using unsafe code, an extremely niche use case. In contrast, anything which keeps a potentially unpin struct behind a pointer has a positive impl of Unpin
. All of the impls of Unpin
in std
are positive polarity, for example.
@withoutboats could you provide a link to the previous discussion around these names where the arguments brought up here have already been argued through?
Here is one thread, though it was certainly also discussed on the RFC thread and tracking issue https://internals.rust-lang.org/t/naming-pin-anchor-move/6864
(the types in this thread called anchor and pin are now called Pin<Box<T>>
and Pin<&'a mut T>
)
Is Unpin supposed to be read as short for Unpinnable? Unpinnable as in you can't actually keep the value pinned, even though it's inside a Pin. (Is that even a correct understanding on my part?)
I've looked through some of the docs and comment threads and didn't see any reference to Unpinnable specifically.
Unpin is not supposed to be short for anything. One thing that I think is not obvious to a lot of users, but is true, is that the standard library style guide is to prefer verbs as trait names, not adjectives - hence Send
, not Sendable
. This hasn't been applied with perfect consistency, but it is the norm. Unpin
is as in "to unpin," as in it is possible to unpin this type from the Pin
you have pinned it with.
Names like Move
(not "moveable," remember) are less clear than Unpin
because they imply that it has to do with being able to move it at all, rather than connecting the behavior to the pin type. You can move !Unpin
types, because you can move any Sized
value in Rust.
Names that are whole phrases as I've seen suggested would be very unidiomatic for std.
It may not be short for it, but that's exactly how I read it. Since traits are verbs, you have to manually transform it to an adjective if you want to use it to describe a type rather than an operation on a type; std::iter::Iterator
is implemented by something that is iterable, std::io::Seek
is implemented by something that is seekable, std::pin::Unpin
is implemented by something that is unpinnable.
@withoutboats any specific problem with some of the other names that don't have the verb issue, e.g. Escape
or EscapePin
? Understood that this discussion has happened before, but presumably a lot more eyes are on this now, so I'm not sure it's a completely redundant rehash...
One thing I think is true is that its unfortunate that Pin
and Unpin
could be read as a pair (some types are "pin" and some are "unpin"), when Pin
is supposed to be a noun, not a verb. The fact that Pin
isn't a trait hopefully clarifies things. The argument in favor of Pinning
makes sense, but here we run up against the name length problem. Especially since method receivers will have to repeat self
twice, we end up with a lot of characters: self: Pinning<&mut Self>
. Not convinced that Pinning<P>
is a whole four characters worth of clarity over Pin<P>
.
@tikue the "escape" terminology is much more overloaded than pinning I think, conflicting with concepts like escape analysis.
Also we had this conversation already, reached a decision to use Pin and Unpin, and none of the arguments brought up in this thread are new.
This rubs me the wrong way -- is the community's experience report undesired? I personally didn't see a clear issue with Unpin
until some of the other comments in this thread, as well as the juxtaposition with Pinned
double negative.
Rust doesn't perform escape analysis, so I'm not sure I see that as a real issue.
:bell: This is now entering its final comment period, as per the review above. :bell:
I'd still like to see documentation improvements as outlined in https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891 before this lands :)
I've opened https://github.com/rust-lang/rust/pull/55992 to add the documentation suggested above and rename Pinned
to PhantomPinned
.
I think Pinned
(and PhantomPinned
) encourages conceptualizing a "pinned" value as one that cannot move out of a Pin
, which means that many values in a Pin
(those whose types impl Unpin
) are not "pinned"!
That seems confusing. I find it easier to conceptualize all values in a Pin
as pinned while they're in the Pin
, and whether or not being pinned is permanent is what the thing formerly named Pinned
controls. A name separate from Pin*
would prevent conflation of two distinct concepts.
PhantomNotUnpin
:P
Personally, I too have a big dislike for Unpin as a name, maybe because it is most often seen as impl !Unpin which reads "not-un-pin" and requires several brain-cycles
Thanks! I have also been bothered by Unpin
for quite some time bot not been able to pin-point (heh) why. Now I think I understand: It's the double-negation.
This is completely the opposite of my experience, manually implementing !Unpin means you're implementing a self-referential structure by hand using unsafe code, an extremely niche use case. In contrast, anything which keeps a potentially unpin struct behind a pointer has a positive impl of Unpin. All of the impls of Unpin in std are positive polarity, for example.
It's not just about implementation though, also about discussion. impl !Sync
is fairly rare (not alone because it is unstable), but talking about Sync
and !Sync
types is quite common. Similarly, !Unpin
came up quite a lot in discussions of this feature, at least the ones I've had.
I, too, would prefer something that positively expresses a property (MoveFromPin
or so). I am not entirely convinced by ergonomics, since unlike Pin
one shouldn't have to write this trait bound quite so often.
Rust doesn't perform escape analysis, so I'm not sure I see that as a real issue.
LLVM does, so escape analysis is still quite relevant to Rust.
The way to solve that is to pick words which invert the meaning of Pin
/ Unpin
. Eg, rename Unpin
to Relocate
. Then !Unpin
becomes !Relocate
. That makes far more intuitive sense to me - I read it as "Oh, objects of this type can't be relocated". Another contender is Movable
.
I'm not sure what the opposite word is that could replace Pin
, or if we even need to. But I could certainly imagine the docs saying something like this:
A Pinned object can be modified directly via
DerefMut
if and only if the object can be relocated in memory.Relocate
is an automatic trait - it is added by default. But if there will be direct pointers to the memory holding your values, opt out ofRelocate
by addingimpl !Relocate
on your type.
impl<T: Relocate> DerefMut for Pin<T> { ... }
This makes far more intuitive sense to me than Unpin
.
I've opened #55992 to add the documentation suggested above
This only adds a part of what was suggested in https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891, though.
I like the MoveFromPin
suggestion. Relocate
is also good, but maybe isn't associated with Pin
enough. One could again understand it as a non-moveable type (which it isn't). RelocateFromPin
would again be good.
Escape
ing is a thing that is also e.g. associated in Swift with closures, and whether they are called inside or outside of the current call chain. That sounds misleading.
I don't see any issues with longer names as long as it helps clarity.
FWIW I'd like to throw in a vote as well to rename Unpin
to something "positive" like Relocate
or MoveFromPin
(or even more verbose but perhaps slightly more accurate MayMoveFromPin
).
I agree that the double negative of !Unpin
or just Unpin
has been confusing to me historically, and something framed in a positive "this can move despite being inside Pin
" I think would help alleviate some confusion!
FWIW I initially thought the same thing about Unpin
, but when I actually went to use it IMO it made sense-- it's not really a double negation, since the operation you're looking for is the ability to Unpin
(take something in and out of Pin
freely) not the ability to keep things in a pin. It's the same as MoveFromPin
, just worded differently. I'd prefer a name that doesn't make people think it's "not Pin
" or something, but IMO MoveFromPin
and others are far too wordy. UndoPin
? (FreePin
for the haskell crowd?)
I still think !Unpin
reads weird – I've trained my inner voice to treat it more like "Don't unpin this!" instead of the usual "Does not implement Unpin
", but it's taken some effort.
What about !Pluck
?
@runiq
instead of the usual "Does not implement Unpin"
I mentioned this in my earlier comment, but I think this is actually a fine way to say it: Unpin
is the operation of taking it in and out of Pin<C<_>>
. Things that don't implement Unpin
don't provide that ability.
@cramertj I quite like UndoPin
@cramertj I do agree that I'm not a huge fan of alternatives proposed so far, but I would personally favor the wordy MoveFromPin
over Unpin
. It's a good point that it's not a double-negative, but on reading it (as someone who hasn't worked with it a ton yet) it keeps tripping me up that it is a double negative. I keep trying to read the "Un" prefix as negative...
I'm curious, but @cramertj or others do you feel like there's a good handle on how much the Unpin
bound ergonomically comes up? Is it super rare? Is it super common? Common enough to be a pain if it's a pain to type?
For a short-and-sweet name I personally like the Relocate
idea, but for longer-and-wordier-but-ok-because-you-don't-type-it-as-much I like MoveFromPin
. I personally feel that regardless of the ergonomics of typing the name both are better than Unpin
I'm curious, but @cramertj or others do you feel like there's a good handle on how much the Unpin bound ergonomically comes up? Is it super rare? Is it super common? Common enough to be a pain if it's a pain to type?
In my experience, there are two cases where Unpin
actually appears in user code:
Unpin
bounds come up because you actually need to move a future around as you poll it (e.g. stuff like some select APIs).Unpin
, because it doesnt matter if those other types are Unpin
since you never pin project to them.An example of the second case is if you have some kind of buffer type youre generic over (e.g. T: AsRef<[u8]>
). You dont need to pin it to get the slice out of it, so you dont care if it implements Unpin
or not, so you just want to say your type unconditionally implements Unpin
so you can implement Future
ignoring pinning.
Unpin
is pretty common to see as a bound-- select!
, StreamExt::next
and other combinators all require the types that they operate on to be Unpin
.
@withoutboats I'm curious about your point 2 there; do we think that impl Unpin
is something that people are going to have to remember to implement often? Similar to how library authors today often forget to #[derive(Debug)]
or impl std::error::Error
for their custom types, which makes it harder to use those libraries?
Unpin is an auto trait. The only time you'll have a type which doesn't implement unpin is when that type explicitly opts out of it. (Or contains a field that opts out of unpin).
I understand that that is the case. And I assumed that that would by far be the most common case, which is why I was surprised that @withoutboats even mentioned the second point. It suggests to me that it may be more common than I originally thought (though perhaps only when implementing your own futures), so I'm curious about the frequency of those kinds of use-cases :)
@alexcrichton I'm curious, but @cramertj or others do you feel like there's a good handle on how much the Unpin bound ergonomically comes up? Is it super rare? Is it super common? Common enough to be a pain if it's a pain to type?
I had written a long post on my experience of porting my code to Futures 0.3 (which uses Pin
). You don't need to read it, the summary is:
Most of the time you don't need to worry about Unpin
at all, because Unpin
is auto-implemented for almost all types.
So the only time you need to worry about Unpin
is:
You have a type which is generic over other types (e.g. struct Foo<A>
).
And you want to implement a pinning API (e.g. Future
/Stream
/Signal
) for that type.
In that case you need to use this:
impl<A> Unpin for Foo<A> where A: Unpin {}
Or this:
impl<A> Unpin for Foo<A> {}
impl<A> Future for Foo<A> where A: Unpin { ... }
That's usually the only situation where Unpin
is needed. As you can see, that usually means needing to use Unpin
~2 times per type.
In the case of non-combinators, or in the case of types which don't implement Future
/Stream
/Signal
, you don't need to use Unpin
at all.
So I would say that Unpin
comes up very rarely, and it only really comes up in the situation of creating Future
/Stream
/Signal
combinators.
So I am strongly in favor of a name like MoveFromPin
. Pinning is a niche feature that most people never need to deal with, so we shouldn't be over-optimizing for name length.
I think the cognitive benefits (avoiding double negation) are much more important than saving a few characters in rare situations.
Especially because pinning is already hard enough to understand! So let's not make it unnecessarily harder.
@jonhoo do we think that
impl Unpin
is something that people are going to have to remember to implement often? Similar to how library authors today often forget to#[derive(Debug)]
orimpl std::error::Error
for their custom types, which makes it harder to use those libraries?
I don't think it's possible to forget to impl Unpin
, because if the author does forget, they'll get a compiler error, which prevents their crate from being published in the first place. So it isn't like #[derive(Debug)]
at all.
The situation @withoutboats is talking about is only for people implementing Future
/Stream
/Signal
combinators, it doesn't affect anybody else (in particular, it doesn't affect downstream users of the library).
(I think double-negation is a part of it, and maybe simply more-negation-than-strictly-necessary also is, but I feel it's not the whole story (trying to introspect here)... "Unpin" is a bit, metaphorical? Indirect? It makes sense when explained, and since "pinning" itself is already a metaphor it's not clear why my brain would have a problem with a taking-further of that metaphor, but, nonetheless, my brain finds the meaning of "unpin" for some reason obscure and hard to latch onto firmly.)
I guess you are right @cramertj, it is not actually double negation in the usual sense, but like @alexcrichton and @glaebhoerl I keep getting tripped by it. "Un-" as a prefix has a very negation-y feeling to it ("unsafe", "unimplemented" and so on is how I usually encounter that prefix), and it is negating pinning here, if only as a verb.
@withoutboats I'm curious about your point 2 there; do we think that impl Unpin is something that people are going to have to remember to implement often? Similar to how library authors today often forget to #[derive(Debug)] or impl std::error::Error for their custom types, which makes it harder to use those libraries?
Absolutely not! If a user is manually implementing a Future
which is generic over a non-future, they probably want to be able to mutate state in their future implementation without writing unsafe code - that is, to treat Pin<&mut Self>
as &mut self
. They will get errors indicating that MyFuture<T: AsRef<[u8]>>
does not implement Unpin
. The best solution to this problem is then to implement Unpin
. But the only impact this has is on the user trying to implement Future
, and its impossible for them to forget because their code will not compile.
The situation @withoutboats is talking about is only for people implementing Future/Stream/Signal combinators, it doesn't affect anybody else (in particular, it doesn't affect downstream users of the library).
I'm specifically talking about non-combinator generic manual futures, which should just have blanket impls of Unpin
even if their generics are not Unpin
.
I made a flowchart for the question "What do I do about pinning when I am implementing a manual future/stream?"
@rfcbot fcp merge Feature name:
pin
Stabilization target: 1.32.0 Tracking issue: #49150 Related RFCs: rust-lang/rfcs#2349This is a proposal to stabilize the
pin
library feature, making the "pinning" APIs for manipulating pinned memory usable on stable.(I've tried to write this proposal as a comprehensive "stabilization report.")
Stabilized feature or APIs
[std|core]::pin::Pin
This stabilizes the
Pin
type in thepin
submodule ofstd
/core
.Pin
is a fundamental, transparent wrapper around a generic typeP
, which is intended to be a pointer type (for example,Pin<&mut T>
andPin<Box<T>>
are both valid, intended constructs). ThePin
wrapper modifies the pointer to "pin" the memory it refers to in place, preventing the user from moving objects out of that memory.The usual way to use the
Pin
type is to construct a pinned variant of some kind of owning pointer (Box
,Rc
, etc). The std library owning pointers all provide apinned
constructor which returns this. Then, to manipulate the value inside, all of these pointers provide a way to degrade towardPin<&T>
andPin<&mut T>
. Pinned pointers can deref, giving you back&T
, but cannot safely mutably deref: this is only possible using the unsafeget_mut
function.As a result, anyone mutating data through a pin will be required to uphold the invariant that they never move out of that data. This allows other code to safely assume that the data is never moved, allowing it to contain (for example) self references.
The
Pin
type will have these stabilized APIs:impl<P> Pin<P> where P: Deref, P::Target: Unpin
fn new(pointer: P) -> Pin<P>
impl<P> Pin<P> where P: Deref
unsafe fn new_unchecked(pointer: P) -> Pin<P>
fn as_ref(&self) -> Pin<&P::Target>
impl<P> Pin<P> where P: DerefMut
fn as_mut(&mut self) -> Pin<&mut P::Target>
fn set(&mut self, P::Target);
impl<'a, T: ?Sized> Pin<&'a T>
unsafe fn map_unchecked<U, F: FnOnce(&T) -> &U>(self, f: F) -> Pin<&'a U>
fn get_ref(self) -> &'a T
impl<'a, T: ?Sized> Pin<&'a mut T>
fn into_ref(self) -> Pin<&'a T>
unsafe fn get_unchecked_mut(self) -> &'a mut T
unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(self, f: F) -> Pin<&'a mut U>
impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin
fn get_mut(self) -> &'a mut T
Trait impls
Most of the trait impls on
Pin
are fairly rote, these two are important to its operation:impl<P: Deref> Deref for Pin<P> { type Target = P::Target }
impl<P: DerefMut> DerefMut for Pin<P> where P::Target: Unpin { }
std::marker::Unpin
Unpin is a safe auto trait which opts out of the guarantees of pinning. If the target of a pinned pointer implements
Unpin
, it is safe to mutably dereference to it.Unpin
types do not have any guarantees that they will not be moved out of aPin
.This makes it as ergonomic to deal with a pinned reference to something that does not contain self-references as it would be to deal with a non-pinned reference. The guarantees of
Pin
only matter for special case types like self-referential structures: those types do not implementUnpin
, so they have the restrictions of thePin
type.Notable implementations of
Unpin
in std:impl<'a, T: ?Sized> Unpin for &'a T
impl<'a, T: ?Sized> Unpin for &'a mut T
impl<T: ?Sized> Unpin for Box<T>
impl<T: ?Sized> Unpin for Rc<T>
impl<T: ?Sized> Unpin for Arc<T>
These codify the notion that pinnedness is not transitive across pointers. That is, a
Pin<&T>
only pins the actual memory block represented byT
in a place. Users have occassionally been confused by this and expected that a type likePin<&mut Box<T>>
pins the data ofT
in place, but it only pins the memory the pinned reference actually refers to: in this case, theBox
's representation, which a pointer into the heap.std::marker::Pinned
The
Pinned
type is a ZST which does not implementUnpin
; it allows you to supress the auto-implementation ofUnpin
on stable, where!Unpin
impls would not be stable yet.Smart pointer constructors
Constructors are added to the std smart pointers to create pinned references:
Box::pinned(data: T) -> Pin<Box<T>>
Rc::pinned(data: T) -> Pin<Rc<T>>
Arc::pinned(data: T) -> Pin<Arc<T>>
Notes on pinning & safety
Over the last 9 months the pinning APIs have gone through several iterations as we have investigated their expressive power and also the soundness of their guarantees. I would now say confidently that the pinning APIs stabilized here are sound and close enough to the local maxima in ergonomics and expressiveness; that is, ready for stabilization.
One of the trickier issues of pinning is determining when it is safe to perform a pin projection: that is, to go from a
Pin<P<Target = Foo>>
to aPin<P<Target = Bar>>
, whereBar
is a field ofFoo
. Fortunately, we have been able to codify a set of rules which can help users determine if such a projection is safe:(Foo: Unpin) implies (Bar: Unpin)
: that is, if it is never the case thatFoo
(the containing type) isUnpin
whileBar
(the projected type) is notUnpin
.Bar
is never moved during the destruction ofFoo
, meaning that eitherFoo
has no destructor, or the destructor is carefully checked to make sure that it never moves out of the field being projected to.Foo
(the containing type) is notrepr(packed)
, because this causes fields to be moved around to realign them.Additionally, the std APIs provide no safe way to pin objects to the stack. This is because there is no way to implement that safely using a function API. However, users can unsafely pin things to the stack by guaranteeing that they never move the object again after creating the pinned reference.
The
pin-utils
crate on crates.io contains macros to assist with both stack pinning and pin projection. The stack pinning macro safely pins objects to the stack using a trick involving shadowing, whereas a macro for projection exists which is unsafe, but avoids you having to write the projection boilerplate in which you could possibly introduce other incorrect unsafe code.Implementation changes prior to stabilization
Unpin
from the prelude, removepin::Unpin
re-exportAs a general rule, we don't re-export things from multiple places in std unless one is a supermodule of the real definition (e.g. shortening
std::collections::hash_map::HashMap
tostd::collections::HashMap
). For this reason, the re-export ofstd::marker::Unpin
fromstd::pin::Unpin
is out of place.At the same time, other important marker traits like Send and Sync are included in the prelude. So instead of re-exporting
Unpin
from thepin
module, by putting in the prelude we make it unnecessary to importstd::marker::Unpin
, the same reason it was put intopin
.Currently, a lot of the associated function of
Pin
do not use method syntax. In theory, this is to avoid conflicting with derefable inner methods. However, this rule has not been applied consistently, and in our experience has mostly just made things more inconvenient. Pinned pointers only implement immutable deref, not mutable deref or deref by value, limiting the ability to deref anyway. Moreover, many of these names are fairly unique (e.g.map_unchecked
) and unlikely to conflict.Instead, we prefer to give the
Pin
methods their due precedence; users who need to access an interior method always can using UFCS, just as they would be required to to access the Pin methods if we did not use method syntax.get_mut_unchecked
toget_unchecked_mut
The current ordering is inconsistent with other uses in the standard library.
impl<P> Unpin for Pin<P>
This impl is not justified by our standard justification for unpin impls: there is no pointer direction between
Pin<P>
andP
. Its usefulness is covered by the impls for pointers themselves.This futures impl will need to change to add a
P: Unpin
bound.Pin
asrepr(transparent)
Pin should be a transparent wrapper around the pointer inside of it, with the same representation.
Connected features and larger milestones
The pin APIs are important to safely manipulating sections of memory which can be guaranteed not to be moved out. If the objects in that memory do not implement
Unpin
, their address will never change. This is necessary for creating self-referential generators and asynchronous functions. As a result, thePin
type appears in the standard libraryfuture
APIs and will soon appear in the APIs for generators as well (#55704).Stabilizing the
Pin
type and its APIs is a necessary precursor to stabilizing theFuture
APIs, which is itself a necessary precursor to stabilizing theasync/await
syntax and moving the entirefutures 0.3
async IO ecosystem onto stable Rust.cc @cramertj @RalfJung