From 1854177aa06930109991dc708da99e7b115b2cea Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Tue, 15 Mar 2022 17:08:03 +0100 Subject: [PATCH 1/5] use impl Future instead of Pin> --- ouroboros_macro/src/info_structures.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ouroboros_macro/src/info_structures.rs b/ouroboros_macro/src/info_structures.rs index a5a65ba..883d30c 100644 --- a/ouroboros_macro/src/info_structures.rs +++ b/ouroboros_macro/src/info_structures.rs @@ -245,7 +245,7 @@ impl StructFieldInfo { let field_type = &self.typ; let return_ty_constructor = || { if make_async { - quote! { ::std::pin::Pin<::std::boxed::Box + 'this>> } + quote! { impl ::core::future::Future + 'this } } else { quote! { #field_type } } @@ -262,7 +262,7 @@ impl StructFieldInfo { let field_type = &self.typ; let return_ty_constructor = || { if make_async { - quote! { ::std::pin::Pin<::std::boxed::Box> + 'this>> } + quote! { impl ::core::future::Future> + 'this } } else { quote! { ::core::result::Result<#field_type, Error_> } } From cda110da916aca5318a5be49c6620355c7073e5a Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Tue, 15 Mar 2022 17:13:50 +0100 Subject: [PATCH 2/5] impl Future can't be used in trait fn, try forcing Send --- ouroboros_macro/src/info_structures.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ouroboros_macro/src/info_structures.rs b/ouroboros_macro/src/info_structures.rs index 883d30c..db9c288 100644 --- a/ouroboros_macro/src/info_structures.rs +++ b/ouroboros_macro/src/info_structures.rs @@ -245,7 +245,7 @@ impl StructFieldInfo { let field_type = &self.typ; let return_ty_constructor = || { if make_async { - quote! { impl ::core::future::Future + 'this } + quote! { ::std::pin::Pin<::std::boxed::Box + Send + 'this>> } } else { quote! { #field_type } } @@ -262,7 +262,7 @@ impl StructFieldInfo { let field_type = &self.typ; let return_ty_constructor = || { if make_async { - quote! { impl ::core::future::Future> + 'this } + quote! { ::std::pin::Pin<::std::boxed::Box> + Send + 'this>> } } else { quote! { ::core::result::Result<#field_type, Error_> } } From 96d0964b89b27996f2666cd615ab22e8faf6814a Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Tue, 15 Mar 2022 18:07:44 +0100 Subject: [PATCH 3/5] Create alternative methods async_send --- ouroboros/src/lib.rs | 10 +-- ouroboros_macro/src/generate/constructor.rs | 42 ++++++----- .../src/generate/summon_checker.rs | 4 +- .../src/generate/try_constructor.rs | 72 ++++++++++--------- ouroboros_macro/src/info_structures.rs | 44 ++++++++---- ouroboros_macro/src/lib.rs | 19 +++-- 6 files changed, 117 insertions(+), 74 deletions(-) diff --git a/ouroboros/src/lib.rs b/ouroboros/src/lib.rs index d32f905..13ba2f8 100644 --- a/ouroboros/src/lib.rs +++ b/ouroboros/src/lib.rs @@ -22,18 +22,18 @@ /// } /// /// fn main() { -/// // The builder is created by the #[self_referencing] macro +/// // The builder is created by the #[self_referencing] macro /// // and is used to create the struct /// let mut my_value = MyStructBuilder { /// int_data: 42, /// float_data: 3.14, /// -/// // Note that the name of the field in the builder -/// // is the name of the field in the struct + `_builder` +/// // Note that the name of the field in the builder +/// // is the name of the field in the struct + `_builder` /// // ie: {field_name}_builder -/// // the closure that assigns the value for the field will be passed +/// // the closure that assigns the value for the field will be passed /// // a reference to the field(s) defined in the #[borrows] macro -/// +/// /// int_reference_builder: |int_data: &i32| int_data, /// float_reference_builder: |float_data: &mut f32| float_data, /// }.build(); diff --git a/ouroboros_macro/src/generate/constructor.rs b/ouroboros_macro/src/generate/constructor.rs index 66f9d07..6ab47bc 100644 --- a/ouroboros_macro/src/generate/constructor.rs +++ b/ouroboros_macro/src/generate/constructor.rs @@ -1,5 +1,5 @@ use crate::{ - info_structures::{ArgType, FieldType, Options, StructInfo}, + info_structures::{ArgType, BuilderType, FieldType, Options, StructInfo}, utils::to_class_case, }; use proc_macro2::{Ident, TokenStream}; @@ -9,7 +9,7 @@ use syn::Error; pub fn create_builder_and_constructor( info: &StructInfo, options: Options, - make_async: bool, + builder_type: BuilderType, ) -> Result<(Ident, TokenStream, TokenStream), Error> { let struct_name = info.ident.clone(); let generic_args = info.generic_arguments(); @@ -19,10 +19,10 @@ pub fn create_builder_and_constructor( } else { syn::parse_quote! { pub(super) } }; - let builder_struct_name = if make_async { - format_ident!("{}AsyncBuilder", info.ident) - } else { - format_ident!("{}Builder", info.ident) + let builder_struct_name = match builder_type { + BuilderType::AsyncSend => format_ident!("{}AsyncSendBuilder", info.ident), + BuilderType::Async => format_ident!("{}AsyncBuilder", info.ident), + BuilderType::Sync => format_ident!("{}Builder", info.ident), }; let documentation = format!( concat!( @@ -67,7 +67,7 @@ pub fn create_builder_and_constructor( for field in &info.fields { let field_name = &field.name; - let arg_type = field.make_constructor_arg_type(&info, make_async)?; + let arg_type = field.make_constructor_arg_type(&info, builder_type)?; if let ArgType::Plain(plain_type) = arg_type { // No fancy builder function, we can just move the value directly into the struct. params.push(quote! { #field_name: #plain_type }); @@ -101,7 +101,7 @@ pub fn create_builder_and_constructor( } } doc_table += &format!(") -> {}: _` | \n", field_name.to_string()); - if make_async { + if builder_type.is_async() { code.push(quote! { let #field_name = #builder_name (#(#builder_args),*).await; }); } else { code.push(quote! { let #field_name = #builder_name (#(#builder_args),*); }); @@ -148,10 +148,10 @@ pub fn create_builder_and_constructor( quote! { #[doc(hidden)] } }; - let constructor_fn = if make_async { - quote! { async fn new_async } - } else { - quote! { fn new } + let constructor_fn = match builder_type { + BuilderType::AsyncSend => quote! { async fn new_async_send }, + BuilderType::Async => quote! { async fn new_async }, + BuilderType::Sync => quote! { fn new }, }; let field_names: Vec<_> = info.fields.iter().map(|field| field.name.clone()).collect(); let constructor_def = quote! { @@ -164,23 +164,27 @@ pub fn create_builder_and_constructor( } }; let generic_where = &info.generics.where_clause; - let builder_fn = if make_async { + let builder_fn = if builder_type.is_async() { quote! { async fn build } } else { quote! { fn build } }; - let builder_code = if make_async { - quote! { + let builder_code = match builder_type { + BuilderType::AsyncSend => quote! { + #struct_name::new_async_send( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Async => quote! { #struct_name::new_async( #(self.#builder_struct_field_names),* ).await - } - } else { - quote! { + }, + BuilderType::Sync => quote! { #struct_name::new( #(self.#builder_struct_field_names),* ) - } + }, }; let builder_def = quote! { #builder_documentation diff --git a/ouroboros_macro/src/generate/summon_checker.rs b/ouroboros_macro/src/generate/summon_checker.rs index caed667..743c199 100644 --- a/ouroboros_macro/src/generate/summon_checker.rs +++ b/ouroboros_macro/src/generate/summon_checker.rs @@ -2,7 +2,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::Error; -use crate::info_structures::{ArgType, StructInfo}; +use crate::info_structures::{ArgType, BuilderType, StructInfo}; pub fn generate_checker_summoner(info: &StructInfo) -> Result { let mut code: Vec = Vec::new(); @@ -12,7 +12,7 @@ pub fn generate_checker_summoner(info: &StructInfo) -> Result Result<(Ident, TokenStream, TokenStream), Error> { let struct_name = info.ident.clone(); let generic_args = info.generic_arguments(); @@ -31,10 +31,10 @@ pub fn create_try_builder_and_constructor( } let mut current_head_index = 0; - let builder_struct_name = if make_async { - format_ident!("{}AsyncTryBuilder", info.ident) - } else { - format_ident!("{}TryBuilder", info.ident) + let builder_struct_name = match builder_type { + BuilderType::AsyncSend => format_ident!("{}AsyncSendTryBuilder", info.ident), + BuilderType::Async => format_ident!("{}AsyncTryBuilder", info.ident), + BuilderType::Sync => format_ident!("{}TryBuilder", info.ident), }; let documentation = format!( concat!( @@ -96,7 +96,7 @@ pub fn create_try_builder_and_constructor( for field in &info.fields { let field_name = &field.name; - let arg_type = field.make_try_constructor_arg_type(info, make_async)?; + let arg_type = field.make_try_constructor_arg_type(info, builder_type)?; if let ArgType::Plain(plain_type) = arg_type { // No fancy builder function, we can just move the value directly into the struct. params.push(quote! { #field_name: #plain_type }); @@ -143,7 +143,7 @@ pub fn create_try_builder_and_constructor( } } doc_table += &format!(") -> Result<{}: _, Error_>` | \n", field_name.to_string()); - let builder_value = if make_async { + let builder_value = if builder_type.is_async() { quote! { #builder_name (#(#builder_args),*).await } } else { quote! { #builder_name (#(#builder_args),*) } @@ -202,22 +202,22 @@ pub fn create_try_builder_and_constructor( } else { quote! { #[doc(hidden)] } }; - let or_recover_ident = if make_async { - quote! { try_new_or_recover_async } - } else { - quote! { try_new_or_recover } + let or_recover_ident = match builder_type { + BuilderType::AsyncSend => quote! { try_new_or_recover_async_send }, + BuilderType::Async => quote! { try_new_or_recover_async }, + BuilderType::Sync => quote! { try_new_or_recover }, }; - let or_recover_constructor_fn = if make_async { + let or_recover_constructor_fn = if builder_type.is_async() { quote! { async fn #or_recover_ident } } else { quote! { fn #or_recover_ident } }; - let constructor_fn = if make_async { - quote! { async fn try_new_async } - } else { - quote! { fn try_new } + let constructor_fn = match builder_type { + BuilderType::AsyncSend => quote! { async fn try_new_async_send }, + BuilderType::Async => quote! { async fn try_new_async }, + BuilderType::Sync => quote! { fn try_new }, }; - let constructor_code = if make_async { + let constructor_code = if builder_type.is_async() { quote! { #struct_name::#or_recover_ident(#(#builder_struct_field_names),*).await.map_err(|(error, _heads)| error) } } else { quote! { #struct_name::#or_recover_ident(#(#builder_struct_field_names),*).map_err(|(error, _heads)| error) } @@ -237,41 +237,49 @@ pub fn create_try_builder_and_constructor( builder_struct_generic_producers.push(quote! { Error_ }); builder_struct_generic_consumers.push(quote! { Error_ }); let generic_where = &info.generics.where_clause; - let builder_fn = if make_async { + let builder_fn = if builder_type.is_async() { quote! { async fn try_build } } else { quote! { fn try_build } }; - let or_recover_builder_fn = if make_async { + let or_recover_builder_fn = if builder_type.is_async() { quote! { async fn try_build_or_recover } } else { quote! { fn try_build_or_recover } }; - let builder_code = if make_async { - quote! { + let builder_code = match builder_type { + BuilderType::AsyncSend => quote! { + #struct_name::try_new_async_send( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Async => quote! { #struct_name::try_new_async( #(self.#builder_struct_field_names),* ).await - } - } else { - quote! { + }, + BuilderType::Sync => quote! { #struct_name::try_new( #(self.#builder_struct_field_names),* ) - } + }, }; - let or_recover_builder_code = if make_async { - quote! { + let or_recover_builder_code = match builder_type { + BuilderType::AsyncSend => quote! { + #struct_name::try_new_or_recover_async_send( + #(self.#builder_struct_field_names),* + ).await + }, + BuilderType::Async => quote! { #struct_name::try_new_or_recover_async( #(self.#builder_struct_field_names),* ).await - } - } else { - quote! { + }, + BuilderType::Sync => quote! { #struct_name::try_new_or_recover( #(self.#builder_struct_field_names),* ) - } + }, }; let builder_def = quote! { #builder_documentation diff --git a/ouroboros_macro/src/info_structures.rs b/ouroboros_macro/src/info_structures.rs index db9c288..a289f37 100644 --- a/ouroboros_macro/src/info_structures.rs +++ b/ouroboros_macro/src/info_structures.rs @@ -41,6 +41,22 @@ pub enum Derive { Eq, } +#[derive(Copy, Clone)] +pub enum BuilderType { + Sync, + Async, + AsyncSend, +} + +impl BuilderType { + pub fn is_async(&self) -> bool { + match self { + BuilderType::Sync => false, + _ => true, + } + } +} + #[derive(Clone)] pub struct StructInfo { pub derives: Vec, @@ -240,15 +256,17 @@ impl StructFieldInfo { pub fn make_constructor_arg_type( &self, info: &StructInfo, - make_async: bool, + builder_type: BuilderType, ) -> Result { let field_type = &self.typ; - let return_ty_constructor = || { - if make_async { - quote! { ::std::pin::Pin<::std::boxed::Box + Send + 'this>> } - } else { - quote! { #field_type } + let return_ty_constructor = || match builder_type { + BuilderType::AsyncSend => { + quote! { ::std::pin::Pin<::std::boxed::Box + ::core::marker::Send + 'this>> } } + BuilderType::Async => { + quote! { ::std::pin::Pin<::std::boxed::Box + 'this>> } + } + BuilderType::Sync => quote! { #field_type }, }; self.make_constructor_arg_type_impl(info, return_ty_constructor) } @@ -257,15 +275,17 @@ impl StructFieldInfo { pub fn make_try_constructor_arg_type( &self, info: &StructInfo, - make_async: bool, + builder_type: BuilderType, ) -> Result { let field_type = &self.typ; - let return_ty_constructor = || { - if make_async { - quote! { ::std::pin::Pin<::std::boxed::Box> + Send + 'this>> } - } else { - quote! { ::core::result::Result<#field_type, Error_> } + let return_ty_constructor = || match builder_type { + BuilderType::AsyncSend => { + quote! { ::std::pin::Pin<::std::boxed::Box> + ::core::marker::Send + 'this>> } + } + BuilderType::Async => { + quote! { ::std::pin::Pin<::std::boxed::Box> + 'this>> } } + BuilderType::Sync => quote! { ::core::result::Result<#field_type, Error_> }, }; self.make_constructor_arg_type_impl(info, return_ty_constructor) } diff --git a/ouroboros_macro/src/lib.rs b/ouroboros_macro/src/lib.rs index 30df517..cd12419 100644 --- a/ouroboros_macro/src/lib.rs +++ b/ouroboros_macro/src/lib.rs @@ -18,6 +18,7 @@ use crate::{ parse::parse_struct, }; use inflector::Inflector; +use info_structures::BuilderType; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenTree; @@ -40,13 +41,17 @@ fn self_referencing_impl( let borrowchk_summoner = generate_checker_summoner(&info)?; let (builder_struct_name, builder_def, constructor_def) = - create_builder_and_constructor(&info, options, false)?; + create_builder_and_constructor(&info, options, BuilderType::Sync)?; let (async_builder_struct_name, async_builder_def, async_constructor_def) = - create_builder_and_constructor(&info, options, true)?; + create_builder_and_constructor(&info, options, BuilderType::Async)?; + let (async_send_builder_struct_name, async_send_builder_def, async_send_constructor_def) = + create_builder_and_constructor(&info, options, BuilderType::AsyncSend)?; let (try_builder_struct_name, try_builder_def, try_constructor_def) = - create_try_builder_and_constructor(&info, options, false)?; + create_try_builder_and_constructor(&info, options, BuilderType::Sync)?; let (async_try_builder_struct_name, async_try_builder_def, async_try_constructor_def) = - create_try_builder_and_constructor(&info, options, true)?; + create_try_builder_and_constructor(&info, options, BuilderType::Async)?; + let (async_send_try_builder_struct_name, async_send_try_builder_def, async_send_try_constructor_def) = + create_try_builder_and_constructor(&info, options, BuilderType::AsyncSend)?; let with_defs = make_with_functions(&info, options)?; let (with_all_struct_defs, with_all_fn_defs) = make_with_all_function(&info, options)?; @@ -76,16 +81,20 @@ fn self_referencing_impl( #borrowchk_summoner #builder_def #async_builder_def + #async_send_builder_def #try_builder_def #async_try_builder_def + #async_send_try_builder_def #with_all_struct_defs #heads_struct_def #impls impl <#generic_params> #struct_name <#(#generic_args),*> #generic_where { #constructor_def #async_constructor_def + #async_send_constructor_def #try_constructor_def #async_try_constructor_def + #async_send_try_constructor_def #(#with_defs)* #with_all_fn_defs #into_heads_fn @@ -95,8 +104,10 @@ fn self_referencing_impl( #visibility use #mod_name :: #struct_name; #extra_visibility use #mod_name :: #builder_struct_name; #extra_visibility use #mod_name :: #async_builder_struct_name; + #extra_visibility use #mod_name :: #async_send_builder_struct_name; #extra_visibility use #mod_name :: #try_builder_struct_name; #extra_visibility use #mod_name :: #async_try_builder_struct_name; + #extra_visibility use #mod_name :: #async_send_try_builder_struct_name; })) } From b0bd26e56ecc5457b7200fd4806a1ec7bdb2a14e Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Tue, 15 Mar 2022 18:31:44 +0100 Subject: [PATCH 4/5] docs --- ouroboros/src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/ouroboros/src/lib.rs b/ouroboros/src/lib.rs index 13ba2f8..7439592 100644 --- a/ouroboros/src/lib.rs +++ b/ouroboros/src/lib.rs @@ -206,6 +206,52 @@ /// } /// ``` /// +/// # Async Send +/// When Send trait is needed, the Send variant of async methods and builders is available. +/// +/// Here is the same example as above in its async send version: +/// +/// ```ignore +/// use ouroboros::self_referencing; +/// +/// #[self_referencing] +/// struct MyStruct { +/// int_data: i32, +/// float_data: f32, +/// #[borrows(int_data)] +/// int_reference: &'this i32, +/// #[borrows(mut float_data)] +/// float_reference: &'this mut f32, +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let mut my_value = MyStructAsyncSendBuilder { +/// int_data: 42, +/// float_data: 3.14, +/// int_reference_builder: |int_data: &i32| Box::pin(async move { int_data }), +/// float_reference_builder: |float_data: &mut f32| Box::pin(async move { float_data }), +/// }.build().await; +/// +/// // Prints 42 +/// println!("{:?}", my_value.borrow_int_data()); +/// // Prints 3.14 +/// println!("{:?}", my_value.borrow_float_reference()); +/// // Sets the value of float_data to 84.0 +/// my_value.with_mut(|fields| { +/// **fields.float_reference = (**fields.int_reference as f32) * 2.0; +/// }); +/// +/// // We can hold on to this reference... +/// let int_ref = *my_value.borrow_int_reference(); +/// println!("{:?}", *int_ref); +/// // As long as the struct is still alive. +/// drop(my_value); +/// // This will cause an error! +/// // println!("{:?}", *int_ref); +/// } +/// ``` +/// /// # What does the macro generate? /// The `#[self_referencing]` struct will replace your definition with an unsafe self-referencing /// struct with a safe public interface. Many functions will be generated depending on your original @@ -238,6 +284,9 @@ /// A basic async constructor. It works identically to the sync constructor differing only in the /// type of closures it expects. Whenever a closure is required it is expected to return a Pinned /// and Boxed Future that Outputs the same type as the synchronous version. +/// ### `MyStruct::new_async_send(fields...) -> MyStruct` +/// An async send constructor. It works identically to the ssync constructor differing only in the +/// Send trait being specified in the return type. /// ### `MyStructBuilder` /// This is the preferred way to create a new instance of your struct. It is similar to using the /// `MyStruct { a, b, c, d }` syntax instead of `MyStruct::new(a, b, c, d)`. It contains one field @@ -251,6 +300,8 @@ /// identically to the synchronous builder differing only in the type of closures it expects. /// Whenever a closure is required it is expected to return a Pinned and Boxed Future that Outputs /// the same type as the synchronous version. +/// ### `MyStructAsyncSendBuilder` +/// Same as MyStructAsyncBuilder, but with Send trait specified in the return type. /// ### `MyStruct::try_new(fields...) -> Result` /// Similar to the regular `new()` function, except the functions wich create values for all /// **self-referencing fields** can return `Result<>`s. If any of those are `Err`s, that error will be @@ -261,10 +312,14 @@ /// **self-referencing fields** can return `Result<>`s. If any of those are `Err`s, that error will be /// returned instead of an instance of `MyStruct`. The preferred way to use this function is through /// `MyStructAsyncTryBuilder` and its `try_build()` function. +/// ### `MyStruct::try_new_async_send(fields...) -> Result` +/// Same as `new_async()` function, but with Send trait specified in the return type. /// ### `MyStruct::try_new_or_recover_async(fields...) -> Result` /// Similar to the `try_new_async()` function, except that all the **head fields** are returned along side /// the original error in case of an error. The preferred way to use this function is through /// `MyStructAsyncTryBuilder` and its `try_build_or_recover()` function. +/// ### `MyStruct::try_new_or_recover_async_Send(fields...) -> Result` +/// Same as `try_new_or_recover_async()` function, but with Send trait specified in the return type. /// ### `MyStruct::with_FIELD(&self, user: FnOnce(field: &FieldType) -> R) -> R` /// This function is generated for every **tail and immutably-borrowed field** in your struct. It /// allows safely accessing From 8ed9b587b90ce24ee030acfa45044c98a479a2f5 Mon Sep 17 00:00:00 2001 From: Marco Napetti Date: Tue, 15 Mar 2022 18:32:14 +0100 Subject: [PATCH 5/5] docs --- ouroboros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ouroboros/src/lib.rs b/ouroboros/src/lib.rs index 7439592..45c76de 100644 --- a/ouroboros/src/lib.rs +++ b/ouroboros/src/lib.rs @@ -318,7 +318,7 @@ /// Similar to the `try_new_async()` function, except that all the **head fields** are returned along side /// the original error in case of an error. The preferred way to use this function is through /// `MyStructAsyncTryBuilder` and its `try_build_or_recover()` function. -/// ### `MyStruct::try_new_or_recover_async_Send(fields...) -> Result` +/// ### `MyStruct::try_new_or_recover_async_send(fields...) -> Result` /// Same as `try_new_or_recover_async()` function, but with Send trait specified in the return type. /// ### `MyStruct::with_FIELD(&self, user: FnOnce(field: &FieldType) -> R) -> R` /// This function is generated for every **tail and immutably-borrowed field** in your struct. It