From eebc0317df3d936a1f5bdc64555366376de45783 Mon Sep 17 00:00:00 2001 From: MrGVSV Date: Tue, 29 Mar 2022 02:10:06 +0000 Subject: [PATCH] bevy_derive: Add derives for `Deref` and `DerefMut` (#4328) # Objective A common pattern in Rust is the [newtype](https://doc.rust-lang.org/rust-by-example/generics/new_types.html). This is an especially useful pattern in Bevy as it allows us to give common/foreign types different semantics (such as allowing it to implement `Component` or `FromWorld`) or to simply treat them as a "new type" (clever). For example, it allows us to wrap a common `Vec` and do things like: ```rust #[derive(Component)] struct Items(Vec); fn give_sword(query: Query<&mut Items>) { query.single_mut().0.push(String::from("Flaming Poisoning Raging Sword of Doom")); } ``` > We could then define another struct that wraps `Vec` without anything clashing in the query. However, one of the worst parts of this pattern is the ugly `.0` we have to write in order to access the type we actually care about. This is why people often implement `Deref` and `DerefMut` in order to get around this. Since it's such a common pattern, especially for Bevy, it makes sense to add a derive macro to automatically add those implementations. ## Solution Added a derive macro for `Deref` and another for `DerefMut` (both exported into the prelude). This works on all structs (including tuple structs) as long as they only contain a single field: ```rust #[derive(Deref)] struct Foo(String); #[derive(Deref, DerefMut)] struct Bar { name: String, } ``` This allows us to then remove that pesky `.0`: ```rust #[derive(Component, Deref, DerefMut)] struct Items(Vec); fn give_sword(query: Query<&mut Items>) { query.single_mut().push(String::from("Flaming Poisoning Raging Sword of Doom")); } ``` ### Alternatives There are other alternatives to this such as by using the [`derive_more`](https://crates.io/crates/derive_more) crate. However, it doesn't seem like we need an entire crate just yet since we only need `Deref` and `DerefMut` (for now). ### Considerations One thing to consider is that the Rust std library recommends _not_ using `Deref` and `DerefMut` for things like this: "`Deref` should only be implemented for smart pointers to avoid confusion" ([reference](https://doc.rust-lang.org/std/ops/trait.Deref.html)). Personally, I believe it makes sense to use it in the way described above, but others may disagree. ### Additional Context Discord: https://discord.com/channels/691052431525675048/692572690833473578/956648422163746827 (controversiality discussed [here](https://discord.com/channels/691052431525675048/692572690833473578/956711911481835630)) --- ## Changelog - Add `Deref` derive macro (exported to prelude) - Add `DerefMut` derive macro (exported to prelude) - Updated most newtypes in examples to use one or both derives Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com> --- crates/bevy_derive/src/derefs.rs | 69 +++++++++++++++++++ crates/bevy_derive/src/lib.rs | 56 +++++++++++++++ crates/bevy_internal/src/prelude.rs | 2 +- examples/2d/contributors.rs | 3 +- examples/2d/many_sprites.rs | 5 +- examples/2d/sprite_sheet.rs | 6 +- examples/3d/many_cubes.rs | 5 +- examples/async_tasks/async_compute.rs | 6 +- .../external_source_external_thread.rs | 6 +- examples/ecs/generic_system.rs | 4 +- examples/ecs/parallel_query.rs | 4 +- examples/ecs/system_chaining.rs | 3 +- examples/ecs/timers.rs | 4 +- examples/game/breakout.rs | 18 ++--- examples/game/game_menu.rs | 6 +- .../shader/compute_shader_game_of_life.rs | 3 +- examples/shader/shader_instancing.rs | 6 +- examples/tools/bevymark.rs | 5 +- 18 files changed, 174 insertions(+), 37 deletions(-) create mode 100644 crates/bevy_derive/src/derefs.rs diff --git a/crates/bevy_derive/src/derefs.rs b/crates/bevy_derive/src/derefs.rs new file mode 100644 index 00000000000000..559b22be19c4cb --- /dev/null +++ b/crates/bevy_derive/src/derefs.rs @@ -0,0 +1,69 @@ +use proc_macro::{Span, TokenStream}; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Index, Member, Type}; + +pub fn derive_deref(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + + let ident = &ast.ident; + let (field_member, field_type) = match get_inner_field(&ast, false) { + Ok(items) => items, + Err(err) => { + return err.into_compile_error().into(); + } + }; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics ::std::ops::Deref for #ident #ty_generics #where_clause { + type Target = #field_type; + + fn deref(&self) -> &Self::Target { + &self.#field_member + } + } + }) +} + +pub fn derive_deref_mut(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + + let ident = &ast.ident; + let (field_member, _) = match get_inner_field(&ast, true) { + Ok(items) => items, + Err(err) => { + return err.into_compile_error().into(); + } + }; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics ::std::ops::DerefMut for #ident #ty_generics #where_clause { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.#field_member + } + } + }) +} + +fn get_inner_field(ast: &DeriveInput, is_mut: bool) -> syn::Result<(Member, &Type)> { + match &ast.data { + Data::Struct(data_struct) if data_struct.fields.len() == 1 => { + let field = data_struct.fields.iter().next().unwrap(); + let member = field + .ident + .as_ref() + .map(|name| Member::Named(name.clone())) + .unwrap_or_else(|| Member::Unnamed(Index::from(0))); + Ok((member, &field.ty)) + } + _ => { + let msg = if is_mut { + "DerefMut can only be derived for structs with a single field" + } else { + "Deref can only be derived for structs with a single field" + }; + Err(syn::Error::new(Span::call_site().into(), msg)) + } + } +} diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index cd0da4668e792e..929eaf67b7bb15 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -2,6 +2,7 @@ extern crate proc_macro; mod app_plugin; mod bevy_main; +mod derefs; mod enum_variant_meta; mod modules; @@ -15,6 +16,61 @@ pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream { app_plugin::derive_dynamic_plugin(input) } +/// Implements [`Deref`] for _single-item_ structs. This is especially useful when +/// utilizing the [newtype] pattern. +/// +/// If you need [`DerefMut`] as well, consider using the other [derive] macro alongside +/// this one. +/// +/// # Example +/// +/// ``` +/// use bevy_derive::Deref; +/// +/// #[derive(Deref)] +/// struct MyNewtype(String); +/// +/// let foo = MyNewtype(String::from("Hello")); +/// assert_eq!(5, foo.len()); +/// ``` +/// +/// [`Deref`]: std::ops::Deref +/// [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html +/// [`DerefMut`]: std::ops::DerefMut +/// [derive]: crate::derive_deref_mut +#[proc_macro_derive(Deref)] +pub fn derive_deref(input: TokenStream) -> TokenStream { + derefs::derive_deref(input) +} + +/// Implements [`DerefMut`] for _single-item_ structs. This is especially useful when +/// utilizing the [newtype] pattern. +/// +/// [`DerefMut`] requires a [`Deref`] implementation. You can implement it manually or use +/// Bevy's [derive] macro for convenience. +/// +/// # Example +/// +/// ``` +/// use bevy_derive::{Deref, DerefMut}; +/// +/// #[derive(Deref, DerefMut)] +/// struct MyNewtype(String); +/// +/// let mut foo = MyNewtype(String::from("Hello")); +/// foo.push_str(" World!"); +/// assert_eq!("Hello World!", *foo); +/// ``` +/// +/// [`DerefMut`]: std::ops::DerefMut +/// [newtype]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html +/// [`Deref`]: std::ops::Deref +/// [derive]: crate::derive_deref +#[proc_macro_derive(DerefMut)] +pub fn derive_deref_mut(input: TokenStream) -> TokenStream { + derefs::derive_deref_mut(input) +} + #[proc_macro_attribute] pub fn bevy_main(attr: TokenStream, item: TokenStream) -> TokenStream { bevy_main::bevy_main(attr, item) diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 148d245beed45a..27a11eb2662b1b 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -5,7 +5,7 @@ pub use crate::{ transform::prelude::*, utils::prelude::*, window::prelude::*, DefaultPlugins, MinimalPlugins, }; -pub use bevy_derive::bevy_main; +pub use bevy_derive::{bevy_main, Deref, DerefMut}; #[doc(hidden)] #[cfg(feature = "bevy_audio")] diff --git a/examples/2d/contributors.rs b/examples/2d/contributors.rs index e4132d299dd3c4..3491a11482aeb1 100644 --- a/examples/2d/contributors.rs +++ b/examples/2d/contributors.rs @@ -27,6 +27,7 @@ struct ContributorSelection { idx: usize, } +#[derive(Deref, DerefMut)] struct SelectTimer(Timer); #[derive(Component)] @@ -161,7 +162,7 @@ fn select_system( mut query: Query<(&Contributor, &mut Sprite, &mut Transform)>, time: Res