Skip to content

Commit c239e97

Browse files
committed
Make generated 'project' reference take an '&mut Pin<&mut Self>'
Based on rust-lang/unsafe-code-guidelines#148 (comment) by @CAD97 Currently, the generated 'project' method takes a 'Pin<&mut Self>', consuming it. This makes it impossible to use the original Pin<&mut Self> after calling project(), since the 'Pin<&mut Self>' has been moved into the the 'Project' method. This makes it impossible to implement useful pattern when working with enums: ```rust enum Foo { Variant1(#[pin] SomeFuture), Variant2(OtherType) } fn process(foo: Pin<&mut Foo>) { match foo.project() { __FooProjection(fut) => { fut.poll(); let new_foo: Foo = ...; foo.set(new_foo); }, _ => {} } } ``` This pattern is common when implementing a Future combinator - an inner future is polled, and then the containing enum is changed to a new variant. However, as soon as 'project()' is called, it becoms imposible to call 'set' on the original 'Pin<&mut Self>'. To support this pattern, this commit changes the 'project' method to take a '&mut Pin<&mut Self>'. The projection types works exactly as before - however, creating it no longer requires consuming the original 'Pin<&mut Self>' Unfortunately, current limitations of Rust prevent us from simply modifiying the signature of the 'project' method in the inherent impl of the projection type. While using 'Pin<&mut Self>' as a receiver is supported on stable rust, using '&mut Pin<&mut Self>' as a receiver requires the unstable `#![feature(arbitrary_self_types)]` For compatibility with stable Rust, we instead dynamically define a new trait, '__{Type}ProjectionTrait', where {Type} is the name of the type with the `#[pin_project]` attribute. This trait looks like this: ```rust trait __FooProjectionTrait { fn project('a &mut self) -> __FooProjection<'a>; } ``` It is then implemented for `Pin<&mut {Type}>`. This allows the `project` method to be invoked on `&mut Pin<&mut {Type}>`, which is what we want. If Generic Associated Types (rust-lang/rust#44265) were implemented and stablized, we could use a single trait for all pin projections: ```rust trait Projectable { type Projection<'a>; fn project(&'a mut self) -> Self::Projection<'a>; } ``` However, Generic Associated Types are not even implemented on nightly yet, so we need for generate a new trait per type for the forseeable future.
1 parent 62b4921 commit c239e97

File tree

7 files changed

+90
-21
lines changed

7 files changed

+90
-21
lines changed

pin-project-internal/src/pin_project/enums.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@ pub(super) fn parse(mut cx: Context, mut item: ItemEnum) -> Result<TokenStream>
2929
let Context { original, projected, lifetime, impl_unpin, .. } = cx;
3030
let proj_generics = proj_generics(&item.generics, &lifetime);
3131
let proj_ty_generics = proj_generics.split_for_impl().1;
32+
let proj_trait = &cx.projected_trait;
3233
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
3334

3435
let mut proj_items = quote! {
3536
enum #projected #proj_generics #where_clause { #(#proj_variants,)* }
3637
};
3738
let proj_method = quote! {
38-
impl #impl_generics #original #ty_generics #where_clause {
39-
fn project<#lifetime>(self: ::core::pin::Pin<&#lifetime mut Self>) -> #projected #proj_ty_generics {
39+
impl #impl_generics #proj_trait #ty_generics for ::core::pin::Pin<&mut #original #ty_generics> #where_clause {
40+
fn project<#lifetime>(&#lifetime mut self) -> #projected #proj_ty_generics #where_clause {
4041
unsafe {
41-
match ::core::pin::Pin::get_unchecked_mut(self) {
42+
match self.as_mut().get_unchecked_mut() {
4243
#(#proj_arms,)*
4344
}
4445
}

pin-project-internal/src/pin_project/mod.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ use syn::{
44
parse::{Parse, ParseStream},
55
punctuated::Punctuated,
66
token::Comma,
7-
Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics, Index, Item, ItemStruct, Lifetime,
8-
LifetimeDef, Meta, NestedMeta, Result, Type,
7+
*
98
};
109

11-
use crate::utils::{crate_path, proj_ident};
10+
use crate::utils::{crate_path, proj_ident, proj_trait_ident};
1211

1312
mod enums;
1413
mod structs;
@@ -51,6 +50,10 @@ struct Context {
5150
original: Ident,
5251
/// Name of the projected type.
5352
projected: Ident,
53+
/// Name of the trait generated
54+
/// to provide a 'project' method
55+
projected_trait: Ident,
56+
generics: Generics,
5457

5558
lifetime: Lifetime,
5659
impl_unpin: ImplUnpin,
@@ -63,7 +66,8 @@ impl Context {
6366
let projected = proj_ident(&original);
6467
let lifetime = proj_lifetime(&generics.params);
6568
let impl_unpin = ImplUnpin::new(generics, unsafe_unpin);
66-
Ok(Self { original, projected, lifetime, impl_unpin, pinned_drop })
69+
let projected_trait = proj_trait_ident(&original);
70+
Ok(Self { original, projected, projected_trait, lifetime, impl_unpin, pinned_drop, generics: generics.clone() })
6771
}
6872

6973
fn impl_drop<'a>(&self, generics: &'a Generics) -> ImplDrop<'a> {
@@ -72,24 +76,47 @@ impl Context {
7276
}
7377

7478
fn parse(args: TokenStream, input: TokenStream) -> Result<TokenStream> {
79+
7580
match syn::parse2(input)? {
7681
Item::Struct(item) => {
77-
let cx = Context::new(args, item.ident.clone(), &item.generics)?;
82+
let mut cx = Context::new(args, item.ident.clone(), &item.generics)?;
83+
let mut res = make_proj_trait(&mut cx)?;
84+
7885
let packed_check = ensure_not_packed(&item)?;
79-
let mut res = structs::parse(cx, item)?;
86+
res.extend(structs::parse(cx, item)?);
8087
res.extend(packed_check);
8188
Ok(res)
8289
}
8390
Item::Enum(item) => {
84-
let cx = Context::new(args, item.ident.clone(), &item.generics)?;
91+
let mut cx = Context::new(args, item.ident.clone(), &item.generics)?;
92+
let mut res = make_proj_trait(&mut cx)?;
93+
8594
// We don't need to check for '#[repr(packed)]',
8695
// since it does not apply to enums
87-
enums::parse(cx, item)
96+
res.extend(enums::parse(cx, item));
97+
Ok(res)
8898
}
8999
item => Err(error!(item, "may only be used on structs or enums")),
90100
}
91101
}
92102

103+
fn make_proj_trait(cx: &mut Context) -> Result<TokenStream> {
104+
let proj_trait = &cx.projected_trait;
105+
let lifetime = &cx.lifetime;
106+
let proj_ident = &cx.projected;
107+
let proj_generics = proj_generics(&cx.generics, &cx.lifetime);
108+
let proj_ty_generics = proj_generics.split_for_impl().1;
109+
110+
let (orig_generics, _orig_ty_generics, orig_where_clause) = cx.generics.split_for_impl();
111+
112+
Ok(quote! {
113+
trait #proj_trait #orig_generics {
114+
fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #orig_where_clause;
115+
}
116+
})
117+
118+
}
119+
93120
fn ensure_not_packed(item: &ItemStruct) -> Result<TokenStream> {
94121
for meta in item.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
95122
if let Meta::List(l) = meta {

pin-project-internal/src/pin_project/structs.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,17 @@ pub(super) fn parse(mut cx: Context, mut item: ItemStruct) -> Result<TokenStream
2626
let impl_drop = cx.impl_drop(&item.generics);
2727
let proj_generics = proj_generics(&item.generics, &cx.lifetime);
2828
let proj_ty_generics = proj_generics.split_for_impl().1;
29+
let proj_trait = &cx.projected_trait;
2930
let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
3031

3132
let mut proj_items = quote! {
3233
struct #proj_ident #proj_generics #where_clause #proj_fields
3334
};
3435
let proj_method = quote! {
35-
impl #impl_generics #orig_ident #ty_generics #where_clause {
36-
fn project<#lifetime>(self: ::core::pin::Pin<&#lifetime mut Self>) -> #proj_ident #proj_ty_generics {
36+
impl #impl_generics #proj_trait #ty_generics for ::core::pin::Pin<&mut #orig_ident #ty_generics> #where_clause {
37+
fn project<#lifetime>(&#lifetime mut self) -> #proj_ident #proj_ty_generics #where_clause {
3738
unsafe {
38-
let this = ::core::pin::Pin::get_unchecked_mut(self);
39+
let this = self.as_mut().get_unchecked_mut();
3940
#proj_ident #proj_init
4041
}
4142
}

pin-project-internal/src/utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ pub(crate) fn proj_ident(ident: &Ident) -> Ident {
77
format_ident!("__{}Projection", ident)
88
}
99

10+
pub(crate) fn proj_trait_ident(ident: &Ident) -> Ident {
11+
format_ident!("__{}ProjectionTrait", ident)
12+
}
13+
1014
pub(crate) trait VecExt {
1115
fn find_remove(&mut self, ident: &str) -> Option<Attribute>;
1216
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
//! }
2323
//!
2424
//! impl<T, U> Foo<T, U> {
25-
//! fn baz(self: Pin<&mut Self>) {
25+
//! fn baz(mut self: Pin<&mut Self>) {
2626
//! let this = self.project();
2727
//! let _: Pin<&mut T> = this.future; // Pinned reference to the field
2828
//! let _: &mut U = this.field; // Normal reference to the field

tests/pin_project.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,22 @@ fn test_pin_project() {
1919

2020
let mut foo = Foo { field1: 1, field2: 2 };
2121

22-
let foo = Pin::new(&mut foo).project();
22+
let mut foo_orig = Pin::new(&mut foo);
23+
let foo = foo_orig.project();
2324

2425
let x: Pin<&mut i32> = foo.field1;
2526
assert_eq!(*x, 1);
2627

2728
let y: &mut i32 = foo.field2;
2829
assert_eq!(*y, 2);
2930

31+
assert_eq!(foo_orig.as_ref().field1, 1);
32+
assert_eq!(foo_orig.as_ref().field2, 2);
33+
3034
let mut foo = Foo { field1: 1, field2: 2 };
3135

32-
let foo = Pin::new(&mut foo).project();
36+
let mut foo = Pin::new(&mut foo);
37+
let foo = foo.project();
3338

3439
let __FooProjection { field1, field2 } = foo;
3540
let _: Pin<&mut i32> = field1;
@@ -42,7 +47,8 @@ fn test_pin_project() {
4247

4348
let mut bar = Bar(1, 2);
4449

45-
let bar = Pin::new(&mut bar).project();
50+
let mut bar = Pin::new(&mut bar);
51+
let bar = bar.project();
4652

4753
let x: Pin<&mut i32> = bar.0;
4854
assert_eq!(*x, 1);
@@ -53,6 +59,7 @@ fn test_pin_project() {
5359
// enum
5460

5561
#[pin_project]
62+
#[derive(Eq, PartialEq, Debug)]
5663
enum Baz<A, B, C, D> {
5764
Variant1(#[pin] A, B),
5865
Variant2 {
@@ -65,7 +72,8 @@ fn test_pin_project() {
6572

6673
let mut baz = Baz::Variant1(1, 2);
6774

68-
let baz = Pin::new(&mut baz).project();
75+
let mut baz_orig = Pin::new(&mut baz);
76+
let baz = baz_orig.project();
6977

7078
match baz {
7179
__BazProjection::Variant1(x, y) => {
@@ -82,9 +90,12 @@ fn test_pin_project() {
8290
__BazProjection::None => {}
8391
}
8492

93+
assert_eq!(Pin::into_ref(baz_orig).get_ref(), &Baz::Variant1(1, 2));
94+
8595
let mut baz = Baz::Variant2 { field1: 3, field2: 4 };
8696

87-
let mut baz = Pin::new(&mut baz).project();
97+
let mut baz = Pin::new(&mut baz);
98+
let mut baz = baz.project();
8899

89100
match &mut baz {
90101
__BazProjection::Variant1(x, y) => {
@@ -110,6 +121,31 @@ fn test_pin_project() {
110121
}
111122
}
112123

124+
#[test]
125+
fn enum_project_set() {
126+
127+
#[pin_project]
128+
#[derive(Eq, PartialEq, Debug)]
129+
enum Bar {
130+
Variant1(#[pin] u8),
131+
Variant2(bool)
132+
}
133+
134+
let mut bar = Bar::Variant1(25);
135+
let mut bar_orig = Pin::new(&mut bar);
136+
let bar_proj = bar_orig.project();
137+
138+
match bar_proj {
139+
__BarProjection::Variant1(val) => {
140+
let new_bar = Bar::Variant2(val.as_ref().get_ref() == &25);
141+
bar_orig.set(new_bar);
142+
},
143+
_ => unreachable!()
144+
}
145+
146+
assert_eq!(bar, Bar::Variant2(true));
147+
}
148+
113149
#[test]
114150
fn where_clause_and_associated_type_fields() {
115151
// struct

tests/pinned_drop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct Foo<'a> {
1414
}
1515

1616
#[pinned_drop]
17-
fn do_drop(foo: Pin<&mut Foo<'_>>) {
17+
fn do_drop(mut foo: Pin<&mut Foo<'_>>) {
1818
**foo.project().was_dropped = true;
1919
}
2020

0 commit comments

Comments
 (0)