Pinning in Plain English
blog.schichler.dev
Pinning in Plain English
1–10 of 22 posts
Re: Pinning in Plain English
#2Sure, in simplest terms, I get it - "Pinning is used to guarantee something is never moved", and the utility of this is mostly (exclusively?) for guarantees in concurrency.
The moment I see the symbol "!Unpin" my brain goes into a fog. "Pinning means things cannot be moved... but most things are Unpin... which can be unpinned after being pinned... but some things are !Unpin... so they can NOT be UN-pinned... so when they are Pinnned they are REALLY pinned..."
It seems to me like "Pin" is a perfectly sound, logical concept, but it was designed by type-system-science nerds, that barely translates to the promise of Rust which is to empower everyone, not just type-system-science nerds, to make safe software.
Re: Pinning in Plain English
#3Alomost feels I need to read the article to the end in order to understand the assumptions at the start.
I guess I’m missing some context.
- what is ‘trivially moveable’? - why would I want to overload the assignment operator? - what is a move constructor? - why would this make memory safe apis tricky? - what does moving references outside unsafe code mean? - what now we suddenly talk about Pin and Unpin, without explaining them first.
I’m so confused
Re: Pinning in Plain English
#4Not to detract from the author's work - it does express the concept in "plain English" as promised - but boy oh boy is Pinning a confusing concept. This is coming from someone who has used rust pretty regularly since 2015. Sure, in simplest terms, I get it - "Pinning is used to guarantee something is never moved", and the utility of this is mostly (exclusively?) for guarantees in concurrency. The moment I see the sym…
For instance if you take a pinned Arc(ex: Pin>) there wasn't a way I could determine to get out the mutable interior pointer(ex: via Arc::get_mut) since those APIs require a specific signature and you would need to un-pin the value to make those calls despite them being by reference(safe from a pinning perspective).
Most of Rust is incredibly well designed but so it's pretty jarring that Pinning is so much harder to use. I've found work-arounds for the cases where I've needed either via boxing or dropping to unsafe but would have been nice if it had worked for my use cases.
Re: Pinning in Plain English
#5This idea is what I've seen get a lot of people lost, but it's pretty natural. By analogy, a DANGER HIGH VOLTAGE sign doesn't itself need to be dangerous or high voltage in order to be useful. If your DANGER HIGH VOLTAGE sign (the sign) is dangerous or high voltage, you'd probably want a second DANGER HIGH VOLTAGE sign pointing out this fact about the first sign. The same for Pin: if you're wanting the pinned pointer (the pointer itself) to not be Unpin, your pinned pointer would need to be pinned by some second pinned pointer.
Re: Pinning in Plain English
#6Not to detract from the author's work - it does express the concept in "plain English" as promised - but boy oh boy is Pinning a confusing concept. This is coming from someone who has used rust pretty regularly since 2015. Sure, in simplest terms, I get it - "Pinning is used to guarantee something is never moved", and the utility of this is mostly (exclusively?) for guarantees in concurrency. The moment I see the sym…
I would largely agree, in the cases where I've needed something pinned I just drop to Box'ing to, keeping the box internal and managing the pinning directly. It's annoying because you lose some classes of things that you may want to pin(values on stack for short durations) but I found similar complexities in the API. For instance if you take a pinned Arc(ex: Pin >) there wasn't a way I could determine to get out the…
The reason you can't get &mut T from Pin> is because the mutable reference lets you move the data in safe Rust, e.g. with std::mem::swap.
So getting the &mut T from a Pin> is not safe from a pinning perspective.
Re: Pinning in Plain English
#7Unfortunately this write-up didn’t help me at all understanding pinning. Alomost feels I need to read the article to the end in order to understand the assumptions at the start. I guess I’m missing some context. - what is ‘trivially moveable’? - why would I want to overload the assignment operator? - what is a move constructor? - why would this make memory safe apis tricky? - what does moving references outside unsaf…
I'll see if I can add a link to an explanation there. In Rust it's mostly a base assumption and many other languages (C#, Java, JS…) avoid the topic almost entirely, so they all don't explain it very prominently. C++ is much more explicit about it, but unfortunately calls this property a bunch of different names. (I've seen "trivially relocatable" as general term for it, since a "move" seems to be a very specific action in C++.)
The intro section turned out a bit C++-y, maybe. You can read up on the terms by clicking any underlined text. I'm not too happy about links not being blue, but I can't change it without being in the Hashnode ambassador program. (Not much spare money means my entire web presence is strung together from free or very inexpensive platform services. Maybe I'll post about that in the future.)
You can also skip the "The Problem" section entirely, though. It's not that important overall and serves mostly as on-ramp for users of other languages with manual memory management.
Re: Pinning in Plain English
#8Unfortunately this write-up didn’t help me at all understanding pinning. Alomost feels I need to read the article to the end in order to understand the assumptions at the start. I guess I’m missing some context. - what is ‘trivially moveable’? - why would I want to overload the assignment operator? - what is a move constructor? - why would this make memory safe apis tricky? - what does moving references outside unsaf…
The classic example of something that is not trivially moveable is something with a pointer into itself. If you just copy the bytes, that pointer still points at the old location, which would not be valid. In C++, you would handle that by having a move constructor update the pointer to point at the new location.
Dealing with non-trivially-moveable types make memory safe APIs tricky because Rust generally guarantees that code with no unsafe block can never result in memory unsafety. This also applies to library design, and anyone designing a library with unsafe blocks in it should design their library to provide the same guarantee — i.e. that anyone using only the safe methods from the library may not be able to cause memory unsafety, even if they can call methods in the library containing unsafe blocks. This should hold no matter how ridiculous the thing they're doing is, as long as it doesn't require unsafe.
So the reason why this is tricky is that safe Rust permits anyone to perform a "trivial move" of any object they have ownership of. Thus, if you make even the constructor of such an object safe, then safe code could call that constructor, then move the object in an unsound way.
So how do you solve this? Any library that has a non-trivially-moveable type needs some way for the user to promise not to move it using a trivial move. The way they do this is by introducing a Pin type, which is a wrapper around a (typically mutable) reference. The primary constructor for Pin is called new_unchecked, and it is an unsafe method whose safety requirement for calling it is that you never move the object that the reference points at. Thus, any library that sees a reference wrapped in a Pin knows that whoever created it _must_ have called an unsafe block, promising to follow the rules for that unsafe method. It is, in a sense, a way to shift the blame if anything goes wrong in an unsafe block in the library — normally the library would be at blame because the library is the only one with an unsafe block, but when using Pin, it is the person who _created_ the Pin object who is at blame for the incorrect use of unsafe — not the library.
It is worth noting that new_unchecked is not the only way to create a Pin. For example, there's a macro called pin! that uses variable shadowing to prevent the user from accessing the underlying variable, preventing the user from moving the object since they can't access the variable name anymore. This lets safe code create a Pin reference to something the safe code owns, and the unsafe block is inside the definition of the macro.
Regarding moving references outside unsafe code, well that's because if someone gets hold of a mutable reference to a pinned value, then they could call std::mem::swap to perform a trivial move of that value, and this would not involve an unsafe block. Thus, if the library exposes any way of getting such a mutable reference in safe code, then that would be an incorrect unsafe block, as safe code is allowed to do anything it wants, as long as it uses only unsafe.
The Pin wrapper type has methods for unwrapping it and getting back the mutable reference that it is a wrapper around, however they are unsafe, so if anyone did that and then called std::mem::swap, they would be at fault because the one who unwrapped it called an unsafe method without following its rules.
As for Unpin, that's a marker on types that are trivially moveable. The Pin wrapper is essentially a no-op for references to those types and provides no guarantees. There's a safe Pin::new method that can only be used for such types.
It's worth pointing out that in Rust, non-trivially-moveable types must still be trivially moveable _until_ they get pinned. So they typically have an "init" state or something like that which can be safely moved around, but then once you pin it, the object becomes non-trivially-moveable.
Re: Pinning in Plain English
#9> Pins are very often `Unpin`, but this isn't relevant to their function. This idea is what I've seen get a lot of people lost, but it's pretty natural. By analogy, a DANGER HIGH VOLTAGE sign doesn't itself need to be dangerous or high voltage in order to be useful. If your DANGER HIGH VOLTAGE sign (the sign) is dangerous or high voltage, you'd probably want a second DANGER HIGH VOLTAGE sign pointing out this fact ab…
It might be a good idea to split the wording thoroughly and officially (into "pinned" vs. "pin"/"pinning …"), since this is such a common sticking point and seemingly mixing concepts right now. (I.e. "pinned pointers" are (usually) "`Pin`-wrapped" but not "pinned in place". It only becomes more muddled once you have inherently-pinning or "add-on" pins without `Pin` in their type.)
Re: Pinning in Plain English
#10Earlier quoted context omitted.
I would largely agree, in the cases where I've needed something pinned I just drop to Box'ing to, keeping the box internal and managing the pinning directly. It's annoying because you lose some classes of things that you may want to pin(values on stack for short durations) but I found similar complexities in the API. For instance if you take a pinned Arc(ex: Pin >) there wasn't a way I could determine to get out the…
> safe from a pinning perspective The reason you can't get &mut T from Pin > is because the mutable reference lets you move the data in safe Rust, e.g. with std::mem::swap. So getting the &mut T from a Pin > is not safe from a pinning perspective.