rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
94.94k stars 12.24k forks source link

[Stabilization] Pin APIs #55766

Closed withoutboats closed 5 years ago

withoutboats commented 5 years ago

@rfcbot fcp merge Feature name: pin Stabilization target: 1.32.0 Tracking issue: #49150 Related RFCs: rust-lang/rfcs#2349

This 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 the pin submodule of std/core. Pin is a fundamental, transparent wrapper around a generic type P, which is intended to be a pointer type (for example, Pin<&mut T> and Pin<Box<T>> are both valid, intended constructs). The Pin 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 a pinned constructor which returns this. Then, to manipulate the value inside, all of these pointers provide a way to degrade toward Pin<&T> and Pin<&mut T>. Pinned pointers can deref, giving you back &T, but cannot safely mutably deref: this is only possible using the unsafe get_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

impl<P> Pin<P> where P: Deref

impl<P> Pin<P> where P: DerefMut

impl<'a, T: ?Sized> Pin<&'a T>

impl<'a, T: ?Sized> Pin<&'a mut T>

impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin

Trait impls

Most of the trait impls on Pin are fairly rote, these two are important to its operation:

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 a Pin.

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 implement Unpin, so they have the restrictions of the Pin type.

Notable implementations of Unpin in std:

These codify the notion that pinnedness is not transitive across pointers. That is, a Pin<&T> only pins the actual memory block represented by T in a place. Users have occassionally been confused by this and expected that a type like Pin<&mut Box<T>> pins the data of T in place, but it only pins the memory the pinned reference actually refers to: in this case, the Box's representation, which a pointer into the heap.

std::marker::Pinned

The Pinned type is a ZST which does not implement Unpin; it allows you to supress the auto-implementation of Unpin 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:

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 a Pin<P<Target = Bar>>, where Bar is a field of Foo. Fortunately, we have been able to codify a set of rules which can help users determine if such a projection is safe:

  1. It is only safe to pin project if (Foo: Unpin) implies (Bar: Unpin): that is, if it is never the case that Foo (the containing type) is Unpin while Bar (the projected type) is not Unpin.
  2. It is only safe if Bar is never moved during the destruction of Foo, meaning that either Foo has no destructor, or the destructor is carefully checked to make sure that it never moves out of the field being projected to.
  3. It is only safe if Foo (the containing type) is not repr(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

As 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 to std::collections::HashMap). For this reason, the re-export of std::marker::Unpin from std::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 the pin module, by putting in the prelude we make it unnecessary to import std::marker::Unpin, the same reason it was put into pin.

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.

The current ordering is inconsistent with other uses in the standard library.

This impl is not justified by our standard justification for unpin impls: there is no pointer direction between Pin<P> and P. Its usefulness is covered by the impls for pointers themselves.

This futures impl will need to change to add a P: Unpin bound.

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, the Pin type appears in the standard library future 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 the Future APIs, which is itself a necessary precursor to stabilizing the async/await syntax and moving the entire futures 0.3 async IO ecosystem onto stable Rust.

cc @cramertj @RalfJung

jonhoo commented 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:

tikue commented 5 years ago

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".

jonhoo commented 5 years ago

@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.

tikue commented 5 years ago

@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
jonhoo commented 5 years ago

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 that P's target will not move until it is dropped. The exception to this is if P's target is Escape. Types marked as Escape 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" a Pin. Escape is an auto-trait, so all types that consist entirely of types that are Escape are themselves also Escape. Implementors can opt out of this on nightly using impl !Escape for T {}, or by including the NoEscape marker from std::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.

yasammez commented 5 years ago

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.

tikue commented 5 years ago

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.

zroug commented 5 years ago

I also like the term escape instead of Unpin but I would go for EscapePin.

Matthias247 commented 5 years ago

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.

jonhoo commented 5 years ago

@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?

tikue commented 5 years ago

@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
Matthias247 commented 5 years ago

@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.

glaebhoerl commented 5 years ago

W.r.t. the latest bikeshedding:

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.

Nemo157 commented 5 years ago

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).

Thomasdezeeuw commented 5 years ago

Also bikshedding the names:

withoutboats commented 5 years ago

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.

jonhoo commented 5 years ago

@withoutboats could you provide a link to the previous discussion around these names where the arguments brought up here have already been argued through?

withoutboats commented 5 years ago

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>)

ghost commented 5 years ago

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.

withoutboats commented 5 years ago

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.

Nemo157 commented 5 years ago

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.

tikue commented 5 years ago

@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...

withoutboats commented 5 years ago

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.

tikue commented 5 years ago

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.

tikue commented 5 years ago

Rust doesn't perform escape analysis, so I'm not sure I see that as a real issue.

rfcbot commented 5 years ago

:bell: This is now entering its final comment period, as per the review above. :bell:

jonhoo commented 5 years ago

I'd still like to see documentation improvements as outlined in https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891 before this lands :)

cramertj commented 5 years ago

I've opened https://github.com/rust-lang/rust/pull/55992 to add the documentation suggested above and rename Pinned to PhantomPinned.

tikue commented 5 years ago

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.

cramertj commented 5 years ago

PhantomNotUnpin :P

RalfJung commented 5 years ago

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.

josephg commented 5 years ago

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 of Relocate by adding impl !Relocate on your type.

impl<T: Relocate> DerefMut for Pin<T> { ... }

This makes far more intuitive sense to me than Unpin.

RalfJung commented 5 years ago

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.

Matthias247 commented 5 years ago

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.

Escapeing 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.

alexcrichton commented 5 years ago

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!

cramertj commented 5 years ago

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?)

runiq commented 5 years ago

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?

cramertj commented 5 years ago

@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.

jonhoo commented 5 years ago

@cramertj I quite like UndoPin

alexcrichton commented 5 years ago

@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

withoutboats commented 5 years ago

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:

  1. Unpin bounds come up because you actually need to move a future around as you poll it (e.g. stuff like some select APIs).
  2. More commonly, if your future is generic only over something other than a future, you usually want to add an unconditional implementation of 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.

cramertj commented 5 years ago

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.

jonhoo commented 5 years ago

@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?

josephg commented 5 years ago

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).

jonhoo commented 5 years ago

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 :)

Pauan commented 5 years ago

@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:

  1. You have a type which is generic over other types (e.g. struct Foo<A>).

  2. 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)] or impl 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).

glaebhoerl commented 5 years ago

(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.)

RalfJung commented 5 years ago

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 commented 5 years ago

@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.

withoutboats commented 5 years ago

I made a flowchart for the question "What do I do about pinning when I am implementing a manual future/stream?"

pinning-flowchart