-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Merged by Bors] - bevy_reflect: Get owned fields #5728
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm on board! Especially for "collection" types like Lists / Arrays / Maps etc.
My only reason to not do this for structs and enums is that it adds more per-type code-gen for something that could be pretty niche. Do you have real use cases for those scenarios? If not, I think we should hold off until absolutely necessary, in the interest of preserving compile time.
I don't really have any actual scenarios. I was just looking at the Here's a rather contrived example of when this could be useful: #[derive(Reflect, FromReflect, Clone, PartialEq, Debug)]
struct Data {
#[reflect(ignore)]
value: String
}
#[derive(Reflect, FromReflect, Clone, PartialEq)]
struct SomeStruct {
data: Data
}
let expected = SomeStruct {
data: Data {
value: String::from("Hello World")
}
};
let dyn_struct: Box<dyn Struct> = Box::new(expected.clone());
// 1.
let field_cloned: Box<dyn Reflect> = dyn_struct.field("data").unwrap().clone_value();
// 2.
let field: Data = <Data as FromReflect>::from_reflect(field_cloned.as_ref()).unwrap();
assert_eq!(expected.data, field); // <- Panic!
// thread 'blah' panicked at 'assertion failed: `(left == right)`
// left: `Data { value: "Hello World" }`,
// right: `Data { value: "" }`', This cause of the failure here is due to ignoring
This is why we end up with a blank string and a panicked assertion. By using something like let dyn_struct: Box<dyn Struct> = Box::new(expected.clone());
let field: Data = dyn_struct.drain().pop().unwrap().take::<Data>().unwrap();
assert_eq!(expected.data, field); Now, I say this is a contrived example since we (1) already know and have the concrete type of the data we want and (2) we could simply update But yeah— no real-world example on hand. I do see that the compile-time cost could make this part of the PR not really worth it right now (or at least in need of more discussion). I can remove the changes on structs, tuple structs, and enums for the time being. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool cool. I see the theoretical value of the struct/enum impls. Happy to re-consider if the need ever shows up, but I appreciate that you removed them in the short term.
This looks good to me!
bors r+ |
# Objective Sometimes it's useful to be able to retrieve all the fields of a container type so that they may be processed separately. With reflection, however, we typically only have access to references. The only alternative is to "clone" the value using `Reflect::clone_value`. This, however, returns a Dynamic type in most cases. The solution there would be to use `FromReflect` instead, but this also has a problem in that it means we need to add `FromReflect` as an additional bound. ## Solution Add a `drain` method to all container traits. This returns a `Vec<Box<dyn Reflect>>` (except for `Map` which returns `Vec<(Box<dyn Reflect>, Box<dyn Reflect>)>`). This allows us to do things a lot simpler. For example, if we finished processing a struct and just need a particular value: ```rust // === OLD === // /// May or may not return a Dynamic*** value (even if `container` wasn't a `DynamicStruct`) fn get_output(container: Box<dyn Struct>, output_index: usize) -> Box<dyn Reflect> { container.field_at(output_index).unwrap().clone_value() } // === NEW === // /// Returns _exactly_ whatever was in the given struct fn get_output(container: Box<dyn Struct>, output_index: usize) -> Box<dyn Reflect> { container.drain().remove(output_index).unwrap() } ``` ### Discussion * Is `drain` the best method name? It makes sense that it "drains" all the fields and that it consumes the container in the process, but I'm open to alternatives. --- ## Changelog * Added a `drain` method to the following traits: * `Struct` * `TupleStruct` * `Tuple` * `Array` * `List` * `Map` * `Enum`
Pull request successfully merged into main. Build succeeded: |
# Objective Sometimes it's useful to be able to retrieve all the fields of a container type so that they may be processed separately. With reflection, however, we typically only have access to references. The only alternative is to "clone" the value using `Reflect::clone_value`. This, however, returns a Dynamic type in most cases. The solution there would be to use `FromReflect` instead, but this also has a problem in that it means we need to add `FromReflect` as an additional bound. ## Solution Add a `drain` method to all container traits. This returns a `Vec<Box<dyn Reflect>>` (except for `Map` which returns `Vec<(Box<dyn Reflect>, Box<dyn Reflect>)>`). This allows us to do things a lot simpler. For example, if we finished processing a struct and just need a particular value: ```rust // === OLD === // /// May or may not return a Dynamic*** value (even if `container` wasn't a `DynamicStruct`) fn get_output(container: Box<dyn Struct>, output_index: usize) -> Box<dyn Reflect> { container.field_at(output_index).unwrap().clone_value() } // === NEW === // /// Returns _exactly_ whatever was in the given struct fn get_output(container: Box<dyn Struct>, output_index: usize) -> Box<dyn Reflect> { container.drain().remove(output_index).unwrap() } ``` ### Discussion * Is `drain` the best method name? It makes sense that it "drains" all the fields and that it consumes the container in the process, but I'm open to alternatives. --- ## Changelog * Added a `drain` method to the following traits: * `Struct` * `TupleStruct` * `Tuple` * `Array` * `List` * `Map` * `Enum`
# Objective Sometimes it's useful to be able to retrieve all the fields of a container type so that they may be processed separately. With reflection, however, we typically only have access to references. The only alternative is to "clone" the value using `Reflect::clone_value`. This, however, returns a Dynamic type in most cases. The solution there would be to use `FromReflect` instead, but this also has a problem in that it means we need to add `FromReflect` as an additional bound. ## Solution Add a `drain` method to all container traits. This returns a `Vec<Box<dyn Reflect>>` (except for `Map` which returns `Vec<(Box<dyn Reflect>, Box<dyn Reflect>)>`). This allows us to do things a lot simpler. For example, if we finished processing a struct and just need a particular value: ```rust // === OLD === // /// May or may not return a Dynamic*** value (even if `container` wasn't a `DynamicStruct`) fn get_output(container: Box<dyn Struct>, output_index: usize) -> Box<dyn Reflect> { container.field_at(output_index).unwrap().clone_value() } // === NEW === // /// Returns _exactly_ whatever was in the given struct fn get_output(container: Box<dyn Struct>, output_index: usize) -> Box<dyn Reflect> { container.drain().remove(output_index).unwrap() } ``` ### Discussion * Is `drain` the best method name? It makes sense that it "drains" all the fields and that it consumes the container in the process, but I'm open to alternatives. --- ## Changelog * Added a `drain` method to the following traits: * `Struct` * `TupleStruct` * `Tuple` * `Array` * `List` * `Map` * `Enum`
Objective
Sometimes it's useful to be able to retrieve all the fields of a container type so that they may be processed separately. With reflection, however, we typically only have access to references.
The only alternative is to "clone" the value using
Reflect::clone_value
. This, however, returns a Dynamic type in most cases. The solution there would be to useFromReflect
instead, but this also has a problem in that it means we need to addFromReflect
as an additional bound.Solution
Add a
drain
method to all container traits. This returns aVec<Box<dyn Reflect>>
(except forMap
which returnsVec<(Box<dyn Reflect>, Box<dyn Reflect>)>
).This allows us to do things a lot simpler. For example, if we finished processing a struct and just need a particular value:
Discussion
drain
the best method name? It makes sense that it "drains" all the fields and that it consumes the container in the process, but I'm open to alternatives.Changelog
drain
method to the following traits:Struct
TupleStruct
Tuple
Array
List
Map
Enum
Changes from initial PR state
Removed
drain
methods onStruct
,TupleStruct
, andEnum
types as they lacked specific use-cases to validate their inclusion in this PR. They may be added back in at some point in the future.