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