diff --git a/ouroboros/src/lib.rs b/ouroboros/src/lib.rs index d32f905..45c76de 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(); @@ -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 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 a5a65ba..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 { + 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>> } - } else { - quote! { #field_type } } + 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 { + 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>> } - } else { - quote! { ::core::result::Result<#field_type, Error_> } } + 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; })) }