owned this note
owned this note
Published
Linked with GitHub
# Reading notes: Safe projections through pin types
https://blog.yoshuawuyts.com/safe-pin-projections-through-view-types/
###### tags: `reading-club`
## Questions
---
Eric: In first view types for projection example:
```rust
// using view types. `timer` is pinned.
fn poll(&mut{ timer, completed } self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { .. }
```
how do we know `timer` is pinned but `completed` is not?
Yosh: good question lol. I'm not sure? I think this was just me handwaving at: "what if that would just work". Later in the post we dive a bit more into details of how that could be made clear via syntax, etc.
Yosh: oh yeah, later on I talk how this could be made to work simply by inferring all `!Unpin` fields be pinned in the projection. But I don't actually end up advocating for that approach.
Eric: This is answered later in the post.
---
Tyler: I think this syntax
```rust
fn poll(&mut{ pin timer, completed } pin self, cx: &mut Context<'_>) -> Poll<Self::Output> {
```
would involve still using `self`, because the view syntax is only "describing the scope of the reference", while something like
```rust
fn poll(self: pin Self { pin timer, completed }, cx: &mut Context<'_>) -> Poll<Self::Output> {
```
would be a destructuring, allowing you to use `timer` and `completed` directly in the method body. Is that right?
Yosh: not sure, let's talk about it!
Yosh: I was thinking view types syntax was destructuring. Eric suggested that we use that syntax if we're already destructuring.
Tyler: Makes sense. Maybe view types syntax is destructuring. I like the idea of reusing the existing syntax in any case.
Eric: Can you destructure in an item function?
Nick: Yep
Nick: I think view types is reifying the restriction of which fields a borrow can capture (like fine-grained capture syntax). Explicitly not destructuring
Yosh: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=907cc42af2602d27f2598632f0bf1720 ??
Eric: Fixed version: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e9362200348d1ad4cc1834da3284afbe
Nick: Not too commonly used since you need to repeat the type name, except for tuples which is where I use it.
Tyler: Might be nice if we removed that restriction, e.g. by removing the need for the type annotation.
---
Yosh: key takeaways I want the async WG to have from this post:
- Even if we reduce the need for most folks to implement futures by hand, it's still worth improving the ergonomics of it.
- In order to make pin projections _safe_, we necessarily require `Unpin` to be _unsafe_ to implement.
Tyler: How does pin_project handle this with `UnsafeUnpin`?
Yosh: I believe it implements `UnsafeUnpin` anytime you use `#[pin_project]`.
Tyler: Marking it `unsafe` over an edition bound doesn't make sense to me since you wouldn't be able to project into a type that implements it from an old edition. Might need
---
Tyler: For fixing `Drop`, what code would the implicitly added `where Self: Unpin` bound break? There must be some code e.g. in pin-project that knows what it's doing.
```rust
impl Drop for Type {
fn drop(&mut self) {
let this = unsafe { Pin::new_unchecked(...) }
}
}
```
..migrate to..
```rust
impl Drop for Type {
fn drop(self: Pin<&mut Self>) where Self: Unpin {
let this = unsafe { Pin::new_unchecked(...) }
}
}
```
Eric: Easy to update pin_project specifically in a way that's compatible.
Tyler: Yeah, seems unlikely that we have _that_ many impls like this floating around in the wild.
Yosh: My intuition as well.
---
Eric: Why is pinning `#[repr(packed)]` a problem?
Yosh: the [`std::pin` docs say so:](https://doc.rust-lang.org/std/pin/index.html)
> Moreover, if your type is `#[repr(packed)]`, the compiler will automatically move fields around to be able to drop them. It might even do that for fields that happen to be sufficiently aligned. As a consequence, you cannot use pinning with a `#[repr(packed)]` type.
Yosh: Would be nice to represent packed in the type system
Tyler: Compiler can check this as long as pin projection is built in.
Nick: Representing layout details like layout and alignment in the type system is going to be a lot.
Nick: I don't quite understand this doc.. why is e.g. transparently moving a value into a register a problem?
Yosh: When dropping things get reshuffled with `#[repr(packed)]`. Something else could live there
Eric: The destructor of the moved field would be able to see that it moved.
Tyler: Rust gets to move your values around.. unless they're pinned.
Tyler: Pin seems to be reliably confusing. One reason why could be that it wraps the pointer instead of your value
Nick: The interaction with `Unpin` too. `Pin` guarantees your type doesn't move unless it's not `Unpin` — that sentence is hard to parse.
Tyler: Yes!
Yosh: Ergonomics are a factor. It feels bad to use.
Eric: Seems more complicated than in other languages for some reason. In C# pinning is operational (used for FFI), whereas in Rust it's a declarative thing.
https://docs.microsoft.com/en-us/dotnet/api/system.memory-1.pin?view=net-6.0
(I don't actually have any experience with pinning in C#, I just know it exists.)
Eric: As soon as I've pinned something, the rest of my project is about how to get access to the pinned thing. At least until you realize that `pin_project` exists.
Yosh: Whole journey of "why do I need `pin_project`". Hasn't meaningfully improved for new users.
Yosh: mcyoung's talk
Tyler: Evidence for me that we probably need to continue having something like pin
Nick: People want self-referential structs but pinning is not a complete answer. You also can't spell the lifetime of a self-referential struct. Might need to be able to say that all references to your struct are pin, maybe you could do that with a special constructor?