Reputation: 6156
The unofficial bevy cheat book explains how queries work:
The first type parameter for a query is the data you want to access. Use
&
for shared/readonly access and&mut
for exclusive/mutable access. UseOption
if the component is not required (you want to access entities with or without that component). If you want multiple components, put them in a tuple.
It implies that I could specify something like this to modify an Optional component and also seeing the entities where it is None:
fn my_mut_system(query: Query<(&Person, Option<&mut Name>)>) {
// I can mutate the Name here, if the person has one.
}
But i couldn't get that to work. (Solution at the end of the question).
While attempting, I ran into an example that compiles but does the wrong thing, and I would like to understand what is going on.
Readonly Optional works:
fn my_second_system(query: Query<(&Person, Option<&Name>)>) {
for (_person,name) in &query {
println!("Hello Again, {:?}", name);
}
}
Fetching only the components that have a Name, also works:
// Fetching Name directly: works
fn my_update_system(query: Query<&Name, With<Person>>) {
for name in &query {
println!("Hello, {}", name.0);
}
}
And if I add a lot of mut
keywords, I can even mutate the name:
fn my_update_system(mut query: Query<&mut Name, With<Person>>) {
for mut name in &mut query {
println!("Hello, {}. I'll call you Bernhard.", name.0);
name.0 = "Bernhard".to_string();
}
}
That all works. The name change is reflected in further systems.
But when I try to also receive the components that do not have a name, I struggle:
// This builds but does the wrong thing!
fn my_mut_system(query: Query<(&Person, Option<&mut Name>)>) {
for (_person, mut name_option) in &query {
if let Some(ref mut name) = &mut name_option {
println!("Hello a third time, {:?}", name);
*name = &Name("Renamedinho".to_string());
} else {
println!("Hello, Anon.");
}
}
}
This example builds and runs without warnings, but the next system (chained after this my_mut_system
) does not see the name change.
The same code, with the type hints that rust-analyzer adds in vscode:
I thought what should happen here is this: I get an Option
that contains a mutable reference to a Name
. If I follow that reference with *name =
, I can assign a new Name
into the Option
. But clearly, that is not what is happening, since the compiler required a *name = &Name
instead of the *name = Name
I was expecting...
Note that trying to avoid that roundabout does not compile:
// This does not work!
fn my_mut_system_one(query: Query<(&Person, Option<&mut Name>)>) {
for (_person, mut name_option) in &query {
// Note: maybe I could even drop both of these ref mut and &mut ?
// The confusion_two.rs file seems to indicate so.
if let Some(name) = &mut name_option {
println!("Hello a third time, {:?}", name);
name.0 = "Renamedinho One".to_string();
} else {
println!("Hello, Anon.");
}
}
}
Even though the type Option<&mut Name>
contains mut
, the compiler says:
error[E0594]: cannot assign to
name.0
, which is behind a&
reference
error[E0594]: cannot assign to `name.0`, which is behind a `&` reference
--> src/main.rs:63:13
|
63 | name.0 = "Renamedinho One".to_string();
| ^^^^^^ cannot assign
For more information about this error, try `rustc --explain E0594`.
error: could not compile `wobbler-repro` (bin "wobbler-repro") due to 1 previous error
In this gist is an example snippet + example output of what i mentioned above.
Making the whole query
mut
allows this:
// This seems to work
fn my_mut_system_two(mut query: Query<(&Person, Option<&mut Name>)>) {
for (_person, mut name_option) in &mut query {
if let Some(name) = &mut name_option {
println!("Hello a third time, {:?}", name);
name.0 = "Succeederinho".to_string();
} else {
println!("Hello another time, Anon.");
}
}
}
my_mut_system
?
my_mut_system
not modify the name permanently?*name = &Name(...
?mut
access to the query and the option?Upvotes: 0
Views: 402
Reputation: 6156
Based on the comments of Chayim Friedman and cafce25, I can now guess at the answers:
In the line below,
if let Some(ref mut name) = &mut name_option {
a name_option
of type &Name
("Reference to Name") is bound mutably. Apparently that results not in a &mut Name
, but in a &mut &Name
("Mutable Reference to Reference to Name").
- Why does my_mut_system not modify the name permanently?
In the line below, which I naively let some previous compiler messages guide me to,
*name = &Name("Renamedinho".to_string());
We dereference the &mut &Name
on the left-hand side and get *name
of type &Name
. We can obviously not assign to (*name).0
, since (*name)
is an immutable reference. That is why my_mut_system_one
did not compile.
When we now assign a new &Name
to *name
, we overwrite only the mut reference, not what it points to.
Why did this compile, if it does not actually have mut access to the query and the option?
Because apparently that's just how the language syntax works. Sometimes, having a way to locally take a mutable reference to some immutable reference can be useful, so it can't be an error.
Later edit because I still don't fully get it and writing it out might help:
I read this rust github discussion to learn that ref mut
means "reference to a mutable something" and has the same effect as &mut
on the right side of the equal sign. So I believe what happened is:
&query
. That means I take the reference to query
and the loop does some magic to loop over query
but return references to the individual entries.mut name_option
ends up being non-mutable, because it can not be when we don't have &mut query
. This is the first place where a compiler error would be appropriate imo.
mut name_option
is of type Option<&Name>
= &mut name_option
, but actually I am simply taking a mutable reference to this Option, so now it is a &mut &Option<&Name>
.Some(ref mut name)
.
&Some(xyz)
does some more magic (Using the Magic Ergonomics RFC to become a Some(&xyz)
instead.&mut &Option<&Name>
into a Option<&mut & &Name>
ref mut name
says that name
should be a reference. So the &mut
inside the Option disappears, leaving me with name: & &Name
.*name = &Name(...)
dereferences the reference name: &&Name
. So *name
is of type &Name
.
Name
.mut
left in my explanation... so why would it be able to assign that?In conclusion, I will have to rephrase my question once I've re-read the rust book.
Upvotes: 0