Let me give slightly more detail: I’m unable to actually evaluate the post from the perspective of someone unfamiliar with
Pin (although I’ve never before seen the idea to put collections
Pin<_>, instead of just types that implement
Deref). What I do find remarkable though is the fact that an introduction that's trying to make
Pin more easy to learn still doesn’t oversimplify anything. In fact, on some points the post is more accurate than the standard library docs itself, because the std docs point out lots of things simply as “you must not do this or that” while the correct / more accurate description is often “you must not do this or that, unless it’s your own type that’s pinned and you know the precise circumstances in which pinning is or isn’t necessary for soundness”. Exactly your point in “Pinning is a matter of perspective”. Similarly, the overview of and references to some use cases of pinning are super important.
Anyways, let me point out a few points that might be improved nonetheless
This is possible because moves in Rust are already pretty explicit once references are involved: The underlying assignment maybe be hidden inside another method, but there is no system that will move heap instances around without being told to do so (unlike for example in C#, where pinning is a runtime operation integrated into the GC API.)
The only exception to this are types that are
Copy, a trait which must be derived explicitly for each type for which implicit trivial copies should be available.
I don’t quite understand what you mean by “The only exception to this are types that are Copy”. First of all, it isn’t clear what “this” is referring to, i.e. I’m wondering “exception from what exactly?” when reading this sentence. (After all, the last point of the previous sentence was some explanation about GC in C#, so that’s definitely not the thing being referenced.) And even if I relate this to “there is no system that will move heap instances around without being told to do so”, it doesn’t make sense. Maybe it relates to “moves in Rust are already pretty explicit” – but
Copy doesn’t really concern moves... maybe something about “explicitness” is related to
Copy, I don’t know.
In fact, the only primitive type that is explicitly
core::marker::PhantomPinned , a marker you can use as member type to make your custom type
!Unpin in stable Rust.
In my mind, the term “primitive type” only refers to these types, as well as tuples, arrays, slices, string slices, references, pointers, function pointers, i.e. the ones that you don’t have to import (even without the prelude), and the ones with special syntax. One page in the reference even only lists boolean, numbers, char, str, and the never type; and calls the rest of non-user-defined types “builtin types”, which then would also include function types, closure types, trait object types, and abstract
impl Trait return types.
Maybe instead of talking about “primitive types” you should use “standard library types”, or something like that?
However, as the type of the pinned instance itself does not change, it can remain visible "unpinned" inside the module that implements a pin in the first place.
Pin<_> hides the normal mutable API only through encapsulation, but can't erase it entirely.
This means that safe code in that module can often move an instance even after it appears pinned to code outside of it, and extra care must be taken to avoid such moves.
I’m not entirely sure what kind of example you have in mind here. It’s certainly the case that when you’re implementing an
!Unpin type and using
unsafe code, then some of your safe code might violate properties necessary for soundness. But that’s more of a general rule for
unsafe code, right? Quite often,
unsafe implementations of e.g. some data structure relies on encapsulation for soundness, and the effect is that safe code in the same module has the power to violate soundness requirements, too. But maybe you’re saying with pinning, it’s particularly easy to accidentally violate the necessary requirements in safe code because moving values is a common thing to do in Rust and often fairly implicit? Nonetheless, it could be more clear that “extra care must be taken” only applies when your code also uses
unsafe, since without it you either cannot work with something like
Pin<&mut T> at all, or you’ll have to use safe abstractions like pin-project, but in the latter case no extra care must be taken anymore.