Skip to content

Conversation

@Diddykonga
Copy link

@Diddykonga Diddykonga commented Jan 26, 2026

Objective / Solution

Add methods for additional ways to get field info on Struct:

Struct::index_of( &self, field: &dyn PartialReflect ) -> Option<usize>
Struct::index_by_name( &self, name: &str ) -> Option<usize> // Rename Migration: `index_of` -> `index_by_name`
Struct::name_of( &self, field: &dyn PartialReflect ) -> Option<&str>
Struct::name_at( &self, index: usize ) -> Option<usize>

Change FieldIter to return an tuple over the names/fields and impl IntoIterator for &dyn Struct for ergonomics.

let struct = MyStruct.reflect_ref();
for (name: &str, field: &dyn PartialReflect) in struct {
    // Do stuff...
}

Split From/Merge After: #22702

Testing

Migrations:
DynamicStruct::index_of(name) -> Struct::index_by_name(name)

Showcase

Click to view showcase
let struct = MyStruct.reflect_ref();
for (name: &str, field: &dyn PartialReflect) in struct {
    // Do stuff...
}

commit 20e6765
Author: Diddykonga <[email protected]>
Date:   Sun Jan 25 16:53:38 2026 -0600

    Suggestion: Change remove_if doc comment

    Co-authored-by: Gino Valente <[email protected]>

commit a8e866c
Author: Diddykonga <[email protected]>
Date:   Sun Jan 25 16:51:57 2026 -0600

    Suggestions: remove_with to remove_if, fix core prefix

commit 07dde6e
Author: Diddykonga <[email protected]>
Date:   Sun Jan 25 16:01:34 2026 -0600

    Fix CI - Update bundle.rs

    Fixed: clippy::semicolon-if-nothing-returned

commit e606839
Author: Diddykonga <[email protected]>
Date:   Sun Jan 25 06:22:03 2026 -0600

    DynamicStruct field removal methods and  Struct QOL
@alice-i-cecile alice-i-cecile added C-Usability A targeted quality-of-life change that makes Bevy easier to use A-Reflection Runtime information about types S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Uncontroversial This work is generally agreed upon D-Straightforward Simple bug fixes and API improvements, docs, test and examples labels Jan 26, 2026
}

fn name_of(&self, field: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<&str> {
#(if #bevy_reflect_path::PartialReflect::reflect_partial_eq(#fields_ref, field).unwrap_or(false) { return #fqoption::Some(#field_names) })*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you can probably clean this up by using ? out of the reflect_partial_eq.

}

fn index_of(&self, field: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<usize> {
#(if #bevy_reflect_path::PartialReflect::reflect_partial_eq(#fields_ref, field).unwrap_or(false) { return #fqoption::Some(#field_indices) })*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above.


impl<'a> Iterator for FieldIter<'a> {
type Item = &'a dyn PartialReflect;
type Item = (&'a str, &'a dyn PartialReflect);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change needs a simple migration guide.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a small migration guide, and your other PR merged, but I like these changes. Particularly the iterator change to also include the names!

@alice-i-cecile alice-i-cecile added M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 26, 2026
@github-actions
Copy link
Contributor

It looks like your PR is a breaking change, but you didn't provide a migration guide.

Please review the instructions for writing migration guides, then expand or revise the content in the migration guides directory to reflect your changes.

Copy link
Member

@MrGVSV MrGVSV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I agree with Struct::index_of and Struct::name_of as they currently stand (i.e. taking in a &dyn PartialReflect). But I do like the other ones!

}

fn name_of(&self, field: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<&str> {
#(if #bevy_reflect_path::PartialReflect::reflect_partial_eq(#fields_ref, field).unwrap_or(false) { return #fqoption::Some(#field_names) })*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in this comment, I'm still not fully sold on this behavior.

To summarize my thoughts, I believe this behavior (i.e. where a field from one struct returns None on a field of another struct of the same type) may be too unintuitive.

I'm on board with index_by_name (though I think it should be renamed to index_of) and name_at, but index_of (in its current form) and name_of seem too situational and easy to mess up.

I'm willing to change my mind if people feel strongly that this is useful/necessary, though.

Comment on lines +71 to +72
/// Gets the name of the field, if it exists.
fn name_of(&self, field: &dyn PartialReflect) -> Option<&str>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably document that this only checks the address of the pointer. And because of this, the method will return None if it's given a reference to the same field from a different struct of the same type or if the field is not the original (i.e. it's a clone of the original).

As mentioned in my comment here, I do think that the address equality is a limitation of the current implementation. Therefore, it may not really belong in the documentation (in case users choose to manually implement it another way that can guarantee it works across struct instances and clones). However, the default implementation from the derive macro is going to be the most common, so I think it's definitely worth noting.

Copy link
Author

@Diddykonga Diddykonga Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behaviour, during the split, was changed to rely upon PartialReflect::reflect_partial_eq, this comment seems assume its still using the previous core::ptr::addr_eq impl? Not sure how much of this coment chain applies with the change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh lol sorry yeah I just assumed it was a direct split and didn't actually check it was changed. My apologies!

However I think the same critique still applies: unless you’re using the same struct or struct with equivalent field value, this will return None. And now this may also return the incorrect Some value in the case that two fields have the same value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, I think both approaches have a downside, we could provide both? but that feels a little meh
name_of/name_of_field + name_at_addr.

}

fn name_of(&self, field: &dyn #bevy_reflect_path::PartialReflect) -> #FQOption<&str> {
#(if #bevy_reflect_path::PartialReflect::reflect_partial_eq(#fields_ref, field).unwrap_or(false) { return #fqoption::Some(#field_names) })*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One idea for getting around the limitations I mention here, is to instead make name_of and friends return a Result with a potential error of like NameOfError::NonUniqueFieldType. And then return that error if the struct contains two of the same type field.

But I don't love that idea as it still is super limiting (just not as much).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option that I think would work better is to provide a low-level API for working with struct field pointers and addresses directly. Then you could do something like:

let api = TheApi::from(&my_struct);
let field: NamedField = api.field_at_addr(&my_struct.field).unwrap();
let name = field.name();

In fact, renaming Struct::name_of to Struct::name_at_addr or something would probably be an okay compromise. But I still also don't know if this sort of low level API belongs in Struct (of course, the benefit is that we don't have to go through get_represented_type_info).

@MrGVSV MrGVSV added X-Contentious There are nontrivial implications that should be thought through and removed X-Uncontroversial This work is generally agreed upon labels Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Reflection Runtime information about types C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Straightforward Simple bug fixes and API improvements, docs, test and examples M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Contentious There are nontrivial implications that should be thought through

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants