bevy_reflect: Reflection-based cloning#13432
Conversation
| fn parse_clone(&mut self, input: ParseStream) -> syn::Result<()> { | ||
| let ident = input.parse::<kw::Clone>()?; | ||
|
|
||
| if input.peek(token::Paren) { |
There was a problem hiding this comment.
I wonder if we should use darling as a helper crate to parse attributes?
It seems easier than what you're doing, especially if the attributes become more complex later on
There was a problem hiding this comment.
We could utilize something like darling in the future (not this PR though). I'm not sure our parsing logic is so complex we really need it, but it's certainly worth considering.
| } | ||
| } | ||
|
|
||
| pub fn get_clone_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> { |
There was a problem hiding this comment.
Where is this used? I couldn't understand this part.
I get the clone_impl on struct,tuple,enum,value,etc. but not this.
Or is this the top-level impl?
There was a problem hiding this comment.
It's used by derive_data.rs. I could probably move this logic into that file directly, since it's the only place where it's used.
cBournhonesque
left a comment
There was a problem hiding this comment.
Looks good to me, I like the change!
I think the naming will be clearer when the PartialReflect/UniqueReflect PR gets merged as well.
Had some small comments, and i'll probably wait for the blocking PR to be merged before approving
0a05f93 to
51f9b2d
Compare
51f9b2d to
ad1146e
Compare
# Objective Fix #10284. ## Solution When `DynamicSceneBuilder` extracts entities, they are cloned via `PartialReflect::clone_value`, making them into dynamic versions of the original components. This loses any custom `ReflectSerialize` type data. Dynamic scenes are deserialized with the original types, not the dynamic versions, and so any component with a custom serialize may fail. In this case `Rect` and `Vec2`. The dynamic version includes the field names 'x' and 'y' but the `Serialize` impl doesn't, hence the "expect float" error. The solution here: Instead of using `clone_value` to clone the components, `FromReflect` clones and retains the original information needed to serialize with any custom `Serialize` impls. I think using something like `reflect_clone` from (#13432) might make this more efficient. I also did the same when deserializing dynamic scenes to appease some of the round-trip tests which use `ReflectPartialEq`, which requires the types be the same and not a unique/proxy pair. I'm not sure it's otherwise necessary. Maybe this would also be more efficient when spawning dynamic scenes with `reflect_clone` instead of `FromReflect` again? An alternative solution would be to fall back to the dynamic version when deserializing `DynamicScene`s if the custom version fails. I think that's possible. Or maybe simply always deserializing via the dynamic route for dynamic scenes? ## Testing This example is similar to the original test case in #10284: ``` rust #![allow(missing_docs)] use bevy::{prelude::*, scene::SceneInstanceReady}; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, (save, load).chain()) .observe(check) .run(); } static SAVEGAME_SAVE_PATH: &str = "savegame.scn.ron"; fn save(world: &mut World) { let entity = world.spawn(OrthographicProjection::default()).id(); let scene = DynamicSceneBuilder::from_world(world) .extract_entity(entity) .build(); if let Some(registry) = world.get_resource::<AppTypeRegistry>() { let registry = registry.read(); let serialized_scene = scene.serialize(®istry).unwrap(); // println!("{}", serialized_scene); std::fs::write(format!("assets/{SAVEGAME_SAVE_PATH}"), serialized_scene).unwrap(); } world.entity_mut(entity).despawn_recursive(); } fn load(mut commands: Commands, asset_server: Res<AssetServer>) { commands.spawn(DynamicSceneBundle { scene: asset_server.load(SAVEGAME_SAVE_PATH), ..default() }); } fn check(_trigger: Trigger<SceneInstanceReady>, query: Query<&OrthographicProjection>) { dbg!(query.single()); } ``` ## Migration Guide The `DynamicScene` format is changed to use custom serialize impls so old scene files will need updating: Old: ```ron ( resources: {}, entities: { 4294967299: ( components: { "bevy_render::camera::projection::OrthographicProjection": ( near: 0.0, far: 1000.0, viewport_origin: ( x: 0.5, y: 0.5, ), scaling_mode: WindowSize(1.0), scale: 1.0, area: ( min: ( x: -1.0, y: -1.0, ), max: ( x: 1.0, y: 1.0, ), ), ), }, ), }, ) ``` New: ```ron ( resources: {}, entities: { 4294967299: ( components: { "bevy_render::camera::projection::OrthographicProjection": ( near: 0.0, far: 1000.0, viewport_origin: (0.5, 0.5), scaling_mode: WindowSize(1.0), scale: 1.0, area: ( min: (-1.0, -1.0), max: (1.0, 1.0), ), ), }, ), }, ) ``` --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
pablo-lua
left a comment
There was a problem hiding this comment.
Very good PR, I just have some small questions :D
| return #FQResult::Err( | ||
| #bevy_reflect_path::ReflectCloneError::FieldNotClonable { | ||
| field: #field_id, | ||
| variant: #FQOption::Some(::std::borrow::Cow::Borrowed(#variant_name)), | ||
| container_type_path: ::std::borrow::Cow::Borrowed(<Self as #bevy_reflect_path::TypePath>::type_path()) | ||
| } | ||
| ) |
There was a problem hiding this comment.
If I understood it right, if we have an ignored field, we can only fallback to Derive impl, right?
|
|
||
| impl_reflect!( | ||
| #[reflect(Debug, Hash, PartialEq, Default)] | ||
| #[reflect(Clone, Debug, Hash, PartialEq, Default)] |
There was a problem hiding this comment.
Question: Should we use #[reflect(Clone)] Whenever possible?
| /// Unlike [`Reflect::clone_value`], which often returns a dynamic representation of `Self`, | ||
| /// this method attempts create a clone of `Self` directly, if possible. | ||
| /// | ||
| /// If the clone cannot be performed, `None` is returned. |
There was a problem hiding this comment.
I think we must warn the user which cases can return None
From what I read in this PR, this returns None if Clone isn't implemented for all types, but when I first read the PR description, this left me very confused: How can Clone fail?
# Objective #16132 introduced entity cloning functionality, and while it works and is useful, it can be made faster. This is the promised follow-up to improve performance. ## Solution **PREFACE**: This is my first time writing `unsafe` in rust and I have only vague idea about what I'm doing. I would encourage reviewers to scrutinize `unsafe` parts in particular. The solution is to clone component data to an intermediate buffer and use `EntityWorldMut::insert_by_ids` to insert components without additional archetype moves. To facilitate this, `EntityCloner::clone_entity` now reads all components of the source entity and provides clone handlers with the ability to read component data straight from component storage using `read_source_component` and write to an intermediate buffer using `write_target_component`. `ComponentId` is used to check that requested type corresponds to the type available on source entity. Reflect-based handler is a little trickier to pull of: we only have `&dyn Reflect` and no direct access to the underlying data. `ReflectFromPtr` can be used to get `&dyn Reflect` from concrete component data, but to write it we need to create a clone of the underlying data using `Reflect`. For this reason only components that have `ReflectDefault` or `ReflectFromReflect` or `ReflectFromWorld` can be cloned, all other components will be skipped. The good news is that this is actually only a temporary limitation: once #13432 lands we will be able to clone component without requiring one of these `type data`s. This PR also introduces `entity_cloning` benchmark to better compare changes between the PR and main, you can see the results in the **showcase** section. ## Testing - All previous tests passing - Added test for fast reflect clone path (temporary, will be removed after reflection-based cloning lands) - Ran miri ## Showcase Here's a table demonstrating the improvement: | **benchmark** | **main, avg** | **PR, avg** | **change, avg** | | ----------------------- | ------------- | ----------- | --------------- | | many components reflect | 18.505 µs | 2.1351 µs | -89.095% | | hierarchy wide reflect* | 22.778 ms | 4.1875 ms | -81.616% | | hierarchy tall reflect* | 107.24 µs | 26.322 µs | -77.141% | | hierarchy many reflect | 78.533 ms | 9.7415 ms | -87.596% | | many components clone | 1.3633 µs | 758.17 ns | -45.937% | | hierarchy wide clone* | 2.7716 ms | 3.3411 ms | +20.546% | | hierarchy tall clone* | 17.646 µs | 20.190 µs | +17.379% | | hierarchy many clone | 5.8779 ms | 4.2650 ms | -27.439% | *: these benchmarks have entities with only 1 component ## Considerations Once #10154 is resolved a large part of the functionality in this PR will probably become obsolete. It might still be a little bit faster than using command batching, but the complexity might not be worth it. ## Migration Guide - `&EntityCloner` in component clone handlers is changed to `&mut ComponentCloneCtx` to better separate data. - Changed `EntityCloneHandler` from enum to struct and added convenience functions to add default clone and reflect handler more easily. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
0a452ba to
d2b787a
Compare
|
Rebased! Some notable changes:
Note that as a followup, it might be nice to add the |
|
Also, I'm curious if we should rename the method since it returns a Any thoughts? |
4e53bd4 to
e97b16e
Compare
e97b16e to
1a2f5b3
Compare
|
Mild preference for |
tychedelia
left a comment
There was a problem hiding this comment.
Don't have any specific comments wrt the implementation here as I'm less familiar with this subsystem, but the code seems good and appears to follow existing patterns for other similar features. I've definitely had use for something like this before.
Tested on macOS.
|
Put up a PR to add |
bushrat011899
left a comment
There was a problem hiding this comment.
Excellent addition! LGTM
|
CI failure looks spurious, trying again. |
# Objective #13432 added proper reflection-based cloning. This is a better method than cloning via `clone_value` for reasons detailed in the description of that PR. However, it may not be immediately apparent to users why one should be used over the other, and what the gotchas of `clone_value` are. ## Solution This PR marks `PartialReflect::clone_value` as deprecated, with the deprecation notice pointing users to `PartialReflect::reflect_clone`. However, it also suggests using a new method introduced in this PR: `PartialReflect::to_dynamic`. `PartialReflect::to_dynamic` is essentially a renaming of `PartialReflect::clone_value`. By naming it `to_dynamic`, we make it very obvious that what's returned is a dynamic type. The one caveat to this is that opaque types still use `reflect_clone` as they have no corresponding dynamic type. Along with changing the name, the method is now optional, and comes with a default implementation that calls out to the respective reflection subtrait method. This was done because there was really no reason to require manual implementors provide a method that almost always calls out to a known set of methods. Lastly, to make this default implementation work, this PR also did a similar thing with the `clone_dynamic ` methods on the reflection subtraits. For example, `Struct::clone_dynamic` has been marked deprecated and is superseded by `Struct::to_dynamic_struct`. This was necessary to avoid the "multiple names in scope" issue. ### Open Questions This PR maintains the original signature of `clone_value` on `to_dynamic`. That is, it takes `&self` and returns `Box<dyn PartialReflect>`. However, in order for this to work, it introduces a panic if the value is opaque and doesn't override the default `reflect_clone` implementation. One thing we could do to avoid the panic would be to make the conversion fallible, either returning `Option<Box<dyn PartialReflect>>` or `Result<Box<dyn PartialReflect>, ReflectCloneError>`. This makes using the method a little more involved (i.e. users have to either unwrap or handle the rare possibility of an error), but it would set us up for a world where opaque types don't strictly need to be `Clone`. Right now this bound is sort of implied by the fact that `clone_value` is a required trait method, and the default behavior of the macro is to use `Clone` for opaque types. Alternatively, we could keep the signature but make the method required. This maintains that implied bound where manual implementors must provide some way of cloning the value (or YOLO it and just panic), but also makes the API simpler to use. Finally, we could just leave it with the panic. It's unlikely this would occur in practice since our macro still requires `Clone` for opaque types, and thus this would only ever be an issue if someone were to manually implement `PartialReflect` without a valid `to_dynamic` or `reflect_clone` method. ## Testing You can test locally using the following command: ``` cargo test --package bevy_reflect --all-features ``` --- ## Migration Guide `PartialReflect::clone_value` is being deprecated. Instead, use `PartialReflect::to_dynamic` if wanting to create a new dynamic instance of the reflected value. Alternatively, use `PartialReflect::reflect_clone` to attempt to create a true clone of the underlying value. Similarly, the following methods have been deprecated and should be replaced with these alternatives: - `Array::clone_dynamic` → `Array::to_dynamic_array` - `Enum::clone_dynamic` → `Enum::to_dynamic_enum` - `List::clone_dynamic` → `List::to_dynamic_list` - `Map::clone_dynamic` → `Map::to_dynamic_map` - `Set::clone_dynamic` → `Set::to_dynamic_set` - `Struct::clone_dynamic` → `Struct::to_dynamic_struct` - `Tuple::clone_dynamic` → `Tuple::to_dynamic_tuple` - `TupleStruct::clone_dynamic` → `TupleStruct::to_dynamic_tuple_struct`
# Objective Now that #13432 has been merged, it's important we update our reflected types to properly opt into this feature. If we do not, then this could cause issues for users downstream who want to make use of reflection-based cloning. ## Solution This PR is broken into 4 commits: 1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that are also `Clone`. This is mandatory as these types would otherwise cause the cloning operation to fail for any type that contains it at any depth. 2. Update the reflection example to suggest adding `#[reflect(Clone)]` on opaque types. 3. Add `#[reflect(clone)]` attributes on all fields marked `#[reflect(ignore)]` that are also `Clone`. This prevents the ignored field from causing the cloning operation to fail. Note that some of the types that contain these fields are also `Clone`, and thus can be marked `#[reflect(Clone)]`. This makes the `#[reflect(clone)]` attribute redundant. However, I think it's safer to keep it marked in the case that the `Clone` impl/derive is ever removed. I'm open to removing them, though, if people disagree. 4. Finally, I added `#[reflect(Clone)]` on all types that are also `Clone`. While not strictly necessary, it enables us to reduce the generated output since we can just call `Clone::clone` directly instead of calling `PartialReflect::reflect_clone` on each variant/field. It also means we benefit from any optimizations or customizations made in the `Clone` impl, including directly dereferencing `Copy` values and increasing reference counters. Along with that change I also took the liberty of adding any missing registrations that I saw could be applied to the type as well, such as `Default`, `PartialEq`, and `Hash`. There were hundreds of these to edit, though, so it's possible I missed quite a few. That last commit is **_massive_**. There were nearly 700 types to update. So it's recommended to review the first three before moving onto that last one. Additionally, I can break the last commit off into its own PR or into smaller PRs, but I figured this would be the easiest way of doing it (and in a timely manner since I unfortunately don't have as much time as I used to for code contributions). ## Testing You can test locally with a `cargo check`: ``` cargo check --workspace --all-features ```
# Objective bevyengine#13432 added proper reflection-based cloning. This is a better method than cloning via `clone_value` for reasons detailed in the description of that PR. However, it may not be immediately apparent to users why one should be used over the other, and what the gotchas of `clone_value` are. ## Solution This PR marks `PartialReflect::clone_value` as deprecated, with the deprecation notice pointing users to `PartialReflect::reflect_clone`. However, it also suggests using a new method introduced in this PR: `PartialReflect::to_dynamic`. `PartialReflect::to_dynamic` is essentially a renaming of `PartialReflect::clone_value`. By naming it `to_dynamic`, we make it very obvious that what's returned is a dynamic type. The one caveat to this is that opaque types still use `reflect_clone` as they have no corresponding dynamic type. Along with changing the name, the method is now optional, and comes with a default implementation that calls out to the respective reflection subtrait method. This was done because there was really no reason to require manual implementors provide a method that almost always calls out to a known set of methods. Lastly, to make this default implementation work, this PR also did a similar thing with the `clone_dynamic ` methods on the reflection subtraits. For example, `Struct::clone_dynamic` has been marked deprecated and is superseded by `Struct::to_dynamic_struct`. This was necessary to avoid the "multiple names in scope" issue. ### Open Questions This PR maintains the original signature of `clone_value` on `to_dynamic`. That is, it takes `&self` and returns `Box<dyn PartialReflect>`. However, in order for this to work, it introduces a panic if the value is opaque and doesn't override the default `reflect_clone` implementation. One thing we could do to avoid the panic would be to make the conversion fallible, either returning `Option<Box<dyn PartialReflect>>` or `Result<Box<dyn PartialReflect>, ReflectCloneError>`. This makes using the method a little more involved (i.e. users have to either unwrap or handle the rare possibility of an error), but it would set us up for a world where opaque types don't strictly need to be `Clone`. Right now this bound is sort of implied by the fact that `clone_value` is a required trait method, and the default behavior of the macro is to use `Clone` for opaque types. Alternatively, we could keep the signature but make the method required. This maintains that implied bound where manual implementors must provide some way of cloning the value (or YOLO it and just panic), but also makes the API simpler to use. Finally, we could just leave it with the panic. It's unlikely this would occur in practice since our macro still requires `Clone` for opaque types, and thus this would only ever be an issue if someone were to manually implement `PartialReflect` without a valid `to_dynamic` or `reflect_clone` method. ## Testing You can test locally using the following command: ``` cargo test --package bevy_reflect --all-features ``` --- ## Migration Guide `PartialReflect::clone_value` is being deprecated. Instead, use `PartialReflect::to_dynamic` if wanting to create a new dynamic instance of the reflected value. Alternatively, use `PartialReflect::reflect_clone` to attempt to create a true clone of the underlying value. Similarly, the following methods have been deprecated and should be replaced with these alternatives: - `Array::clone_dynamic` → `Array::to_dynamic_array` - `Enum::clone_dynamic` → `Enum::to_dynamic_enum` - `List::clone_dynamic` → `List::to_dynamic_list` - `Map::clone_dynamic` → `Map::to_dynamic_map` - `Set::clone_dynamic` → `Set::to_dynamic_set` - `Struct::clone_dynamic` → `Struct::to_dynamic_struct` - `Tuple::clone_dynamic` → `Tuple::to_dynamic_tuple` - `TupleStruct::clone_dynamic` → `TupleStruct::to_dynamic_tuple_struct`
…13723) # Objective Addresses [comments](bevyengine#7317 (comment)) regarding bevyengine#7317 (note that this doesn't replace bevyengine#7317, there are still some great improvements there besides this syntactical problem). There currently exist some "special" type data registrations that can be registered like other type data (e.g. `#[reflect(Hash)]`) or can use a "special" syntax to allow specifying custom implementations (e.g. `#[reflect(Hash(custom_hash_fn))]`). And there may be more to follow (bevyengine#13432). What's interesting is that most of these special cased registrations don't actually come with any type data type. Instead, they simply modify methods on `Reflect` (e.g. `Reflect::reflect_hash`). bevyengine#7317 sought to distinguish between these "special" registrations by making them lowercase and use a more conventional attribute style: `#[reflect(hash = "custom_hash_fn")]`. However, while this did help distinguish these registrations and make them a bit prettier, they now require the user to actually know which traits are "special" and which are not (as pointed out [here](bevyengine#7317 (comment))). Ideally, users shouldn't have to know which traits are "special" until they need to. For most users, they should just know that they need to register their trait in order for certain things to work. And the special-casing may be easier to follow if we open up the configuration abilities to _all_ type data. ## Solution This PR introduces `CreateTypeData` which replaces `FromType`. This was done for two reasons. Firstly, `FromType` isn't very descriptive as to what it should be used for. We are creating type data from a type, but it's not immediately clear this is even for type data. Renaming to `CreateTypeData` should hopefully make this much clearer. Secondly, in order to support type data with parameters like the `custom_hash_fn` in `reflect(Hash(custom_hash_fn))`, an additional `Input` type parameter had to be added. This makes the new signature `CreateTypeData<T, Input = ()>`. We can now create type data that accepts input! ```rust trait Combine { fn combine(a: f32, b: f32) -> f32; } #[derive(Clone)] struct ReflectCombine { multiplier: f32, additional: f32, combine: fn(f32, f32) -> f32, } impl ReflectCombine { pub fn combine(&self, a: f32, b: f32) -> f32 { let combined = (self.combine)(a, b); let multiplied = self.multiplier * combined; multiplied + self.additional } } impl<T: Combine + Reflect> CreateTypeData<T, (f32, f32)> for ReflectCombine { fn create_type_data(input: (f32, f32)) -> Self { Self { multiplier: input.0, additional: input.1, combine: T::combine, } } } ``` And then register them with the special function-like syntax: ```rust #[derive(Reflect)] #[reflect(Combine(2.0, 4.0))] struct Foo; ``` The above code will compile into the following registration: ```rust registration.insert(<ReflectCombine as CreateTypeData<Self, _>>::create_type_data((2.0, 4.0))) ``` Notice how the macro automatically generates the tuple for us, so we don't have to add an additional layer of parentheses. ### Multiple Input Types You might be wondering why we're using a type parameter instead of an associated type to specify the input type. An associated type would limit us to a single implementation. This means that if we want to support the type data with optional parameters (e.g. support both `Hash` and `Hash(custom_hash_fn)`), then all type data must take in `Option<Self::Input>`, regardless of whether or not a `None` case is supported. This is important because the macro has to be pass in _something_, whether that be `()` or `None`. By using a type parameter we open the door to type data with required input: ```rust // `ReflectMyTrait` must be registered with input impl<T> CreateTypeData<T, u32> for ReflectMyTrait { fn create_type_data(input: u32) -> Self { Self { value: input, } } } // And we can support all different input types impl<T> CreateTypeData<T, i32> for ReflectMyTrait { fn create_type_data(input: i32) -> Self { Self { value: input.abs() as u32, } } } ``` However, this may be something we don't necessarily care about since users could also get away with this using custom input enums. And the required-input case could be deferred until runtime (i.e. maybe a panic in the `None` case). ### Adding `ReflectPartialEq` and `ReflectHash` I had originally considered adding `ReflectPartialEq` and `ReflectHash` type data to further decrease the differences between the "special" registrations and the regular ones. However, I chose not to do that to (1) reduce the complexity of this PR and (2) we may end up removing these entirely due to bevyengine#8695. ### What else is this good for? Another question you might have is what else this is good for beyond just making things a bit more consistent. I'm not sure exactly how the community will use it, but I can see it being used for things like feature gating certain functionality: ```rust #[derive(Reflect)] #[cfg_attr(feature = "debug", reflect(MyTrait(true)))] #[cfg_attr(not(feature = "debug"), reflect(MyTrait(false)))] struct Foo; ``` Or to emulate specialization via reflection: ```rust impl<T> DoSomething for T { fn do_something(&self) { println!("Doing the same old stuff."); } } #[derive(Reflect)] #[reflect(ReflectDoSomething(|_| { println!("Doing something special!"); }))] struct Foo; ``` Note that all of the above could always be done with manual registration. However, due to them requiring input, some cases could _only_ be done with manual registration. This PR mainly opens the door to doing more of this interesting stuff with type data via the macro registration. It not only unifies "special" and regular registrations, but also manual and automatic registrations. ## Testing The tests for this feature are split into doctests (for the docs on `CreateTypeData`) and in the compile-fail tests. These will both be verified automatically by CI. --- ## Changelog - Replaced `FromType<T>` with `CreateTypeData<T, Input = ()>` - Type data may now opt-in to accepting input during creation using the `#[reflect(MyTrait(...))]` syntax - Added `TypeRegistry::register_type_data_with` method ## Migration Guide `FromType<T>` has been replaced by `CreateTypeData<T, Input = ()>`. Implementors of `FromType<T>` will need to update their implementation: ```rust // BEFORE impl<T> FromType<T> for ReflectMyTrait { fn from_type() -> Self { // ... } } // AFTER impl<T> CreateTypeData<T> for ReflectMyTrait { fn create_type_data(input: ()) -> Self { // ... } } ``` Additionally, any calls made to `FromType::from_type` will need to be updated as well: ```rust // BEFORE <ReflectMyTrait as FromType<Foo>>::from_type() // AFTER <ReflectMyTrait as CreateTypeData<Foo>>::create_type_data(()) ```
Objective
Using
Reflect::clone_valuecan be somewhat confusing to those unfamiliar with how Bevy's reflection crate works. For example take the following code:What can we expect to be the underlying type of
clone? If you guessedusize, then you're correct! Let's try another:What about this code? What is the underlying type of
clone? If you guessedFoo, unfortunately you'd be wrong. It's actuallyDynamicStruct.It's not obvious that the generated
Reflectimpl actually callsStruct::clone_dynamicunder the hood, which always returnsDynamicStruct.There are already some efforts to make this a bit more apparent to the end-user: #7207 changes the signature of
Reflect::clone_valueto instead returnBox<dyn PartialReflect>, signaling that we're potentially returning a dynamic type.But why can't we return
Foo?Foocan obviously be cloned— in fact, we already derivedCloneon it. But even without the derive, this seems like somethingReflectshould be able to handle. Almost all types that implementReflecteither contain no data (trivially clonable), they contain a#[reflect_value]type (which, by definition, must implementClone), or they contain anotherReflecttype (which recursively fall into one of these three categories).This PR aims to enable true reflection-based cloning where you get back exactly the type that you think you do.
Solution
Add a
Reflect::reflect_clonemethod which returnsResult<Box<dyn Reflect>, ReflectCloneError>, where theBox<dyn Reflect>is guaranteed to be the same type asSelf.Notice that we didn't even need to derive
Clonefor this to work: it's entirely powered via reflection!Under the hood, the macro generates something like this:
If we did derive
Clone, we can tellReflectto rely on that instead:Generated Code
Or, we can specify our own cloning function:
Generated Code
Similarly, we can specify how fields should be cloned. This is important for fields that are
#[reflect(ignore)]'d as we otherwise have no way to know how they should be cloned.Generated Code
If we don't supply a
cloneattribute for an ignored field, then the method will automatically returnErr(ReflectCloneError::FieldNotClonable {/* ... */}).Errvalues "bubble up" to the caller. So ifFoocontainsBarand thereflect_clonemethod forBarreturnsErr, then thereflect_clonemethod forFooalso returnsErr.Attribute Syntax
You might have noticed the differing syntax between the container attribute and the field attribute.
This was purely done for consistency with the current attributes. There are PRs aimed at improving this. #7317 aims at making the "special-cased" attributes more in line with the field attributes syntactically. And #9323 aims at moving away from the stringified paths in favor of just raw function paths.
Compatibility with Unique Reflect
This PR was designed with Unique Reflect (#7207) in mind. This method actually wouldn't change that much (if at all) under Unique Reflect. It would still exist on
Reflectand it would stillOption<Box<dyn Reflect>>. In fact, Unique Reflect would only improve the user's understanding of what this method returns.We may consider moving what's currently
Reflect::clone_valuetoPartialReflectand possibly renaming it topartial_reflect_cloneorclone_dynamicto better indicate how it differs fromreflect_clone.Testing
You can test locally by running the following command:
Changelog
Reflect::reflect_clonemethodReflectCloneErrorerror enum#[reflect(Clone)]container attribute#[reflect(clone)]field attribute