Skip to content

Mutable NestedQuery by relaxing ReadOnly::State == State constraint - #25652

Closed
chescock wants to merge 7 commits into
bevyengine:mainfrom
chescock:mutable-nested-query
Closed

chescock wants to merge 7 commits into
bevyengine:mainfrom
chescock:mutable-nested-query

Conversation

@chescock

@chescock chescock commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Objective

Support mutable queries using NestedQuery.

The reason this was not supported in the initial version is that we want NestedQuery<D, F>::ReadOnly == NestedQuery<D::ReadOnly, F>, but that doesn't satisfy the ReadOnly::State == State constraint since QueryState<D::ReadOnly, F> != QueryState<D, F>.

Solution

Remove the ReadOnly::State == State constraint, and replace it with a safety requirement. QueryState<D, F> could always be transmuted to QueryState<D::ReadOnly, F>, so the outer transmute is still valid.

Note that the type constraint was merely a lint here. It was never enough for the states to be the same type, they also had to have compatible values! For example, <&T>::State == ComponentId, but any value other than the ComponentId of T would be unsound. Make this requirement explicit, and update the safety comments of mutable query types to show that they satisfy it.

See also #25642 for an alternative approach.

@tim-blackbird tim-blackbird added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events X-Uncontroversial This work is generally agreed upon D-Unsafe Touches with unsafe code in some way S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 2, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in ECS Sep 2, 2026
@tim-blackbird

Copy link
Copy Markdown
Contributor

This is lovely, much preferred over the state transmute hokey pokey I was doing in #25642 :)

+1 on the removal of the ReadOnly::State == State constraint. This bound prevents some misuse when dealing with D::State directly, but it cannot help prove that e.g. QueryState<D> is valid for QueryState<D::ReadOnly>. As you mentioned, it doesn't help at all with correctness of the value.

I'm liking the changes to the safety comments a lot!

@hymm
hymm self-requested a review September 14, 2026 22:02
@alice-i-cecile alice-i-cecile added the D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes label Sep 14, 2026
///
/// - Component access of `Self::ReadOnly` must be a subset of `Self`
/// and `Self::ReadOnly` must match exactly the same archetypes/tables as `Self`
/// - It must be valid to transmute `Self::State` to `Self::ReadOnly::State`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Extremely clear and helpful!

// SAFETY: access of `FilteredEntityRef` is a subset of `FilteredEntityMut`
// SAFETY:
// - `State` is `Access` for both `FilteredEntityMut` and `FilteredEntityRef`.
// `FilteredEntityRef` accepts any `Access`, so the transmute is always valid.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: I think these safety comments would be clearer if they explained that FilteredEntityRef etc have maximal access, and so any transmutation is necessarily a non-strict subset.

Comment thread crates/bevy_ecs/src/query/fetch.rs Outdated
/// - Component access of `Self::ReadOnly` must be a subset of `Self`
/// and `Self::ReadOnly` must match exactly the same archetypes/tables as `Self`
/// - It must be valid to transmute `Self::State` to `Self::ReadOnly::State`,
/// and the resulting `Self::ReadOnly` must have a subset of the access of `Self`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
/// and the resulting `Self::ReadOnly` must have a subset of the access of `Self`
/// and the resulting `Self::ReadOnly` must have a non-strict subset of the access of `Self`


/// The read-only variant of this [`QueryData`], which satisfies the [`ReadOnlyQueryData`] trait.
type ReadOnly: ReadOnlyQueryData<State = <Self as WorldQuery>::State>;
type ReadOnly: ReadOnlyQueryData;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Much simpler.

///
/// # Safety
///
/// - Component access of `Self::ReadOnly` must be a subset of `Self`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This change is a meaningful change to our safety invariants here, and important to any manual implementation, but any impl which met this also meets the new variants.

Is this worth putting in a migration guide? Like, on the one hand this is non-breaking, but on the other hand I would certainly want to be alerted to this so I can update my safety contract.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rust is working towards Safety Contracts as Types https://internals.rust-lang.org/t/pre-rfc-unsafe-reasons/22093/1
Which means in the future, such a thing would indeed be a breaking change, so perhaps we make it idiom early?

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm basically good to approve this, but I have a couple of nits to discuss :) Very cool to see this lifted.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two other notes caught by AI-augmented review:

  1. The QueryData derive fails for mutable nested queries. Important to fix eventually obviously, but not critical to do now.
  2. There's stale doc comments saying that this doesn't work for mutable queries (line 2815 and 2960 in fetch.rs).

@chescock

Copy link
Copy Markdown
Contributor Author
  1. The QueryData derive fails for mutable nested queries. Important to fix eventually obviously, but not critical to do now.

Argh, when looking into how this was different from tuples, I managed to convince myself that the tuple implementations are unsound and we can't take this approach :(.

The problem is that if we have (D1, NestedQuery<D2>), then we're asserting that (D1::State, QueryState<D2>) can be transmuted to (D1::State, QueryState<D2::ReadOnly>). But tuples are #[repr(rust)], so even if the QueryState part is valid the tuples themselves can't be transmuted!

So I think we need to abandon this approach :(. The simplest alternative is the one in #25642, where we store a QueryState<D, F> by transmuting it to QueryState<D::ReadOnly, F> and back.

(The other approach I've come up with is

#[reprt(transparent)]
struct QueryState<D: QueryData, F: QueryFilter>(QueryStateInner<D::State, F::State>);

And then using QueryStateInner in NestedQuery::State. That has the advantage of giving a better layout for QueryState by avoiding #[repr(c)] and permitting field reordering, but the safety justifications are just as complex and we'd need to add .0 everywhere.)

I'll push the doc changes I made in response to the other comments, since we'll want the changes to the NestedQuery docs regardless of the approach, and we may still want a PR to clarify the safety requirement that the State values are valid for ReadOnly::State.

@tim-blackbird Would you like to re-open #25642 and get a bunch of suggestions for documentation changes, or would you prefer that I adopt the PR?

@chescock chescock added S-Blocked This cannot move forward until something else changes and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 15, 2026
@tim-blackbird

Copy link
Copy Markdown
Contributor

Sorry to hear this didn't work out. I would like it if you could adopt #25642 for me :)

@chescock

Copy link
Copy Markdown
Contributor Author

Closing in favor of #25809

@chescock chescock closed this Sep 16, 2026
@github-project-automation github-project-automation Bot moved this from Needs SME Triage to Done in ECS Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes D-Unsafe Touches with unsafe code in some way S-Blocked This cannot move forward until something else changes X-Uncontroversial This work is generally agreed upon

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants