How can I iterate on a Bevy Query and keep a reference to the iterated value so that I can use it later?

2.1k Views Asked by At

I have a borrow in the empty variable and I want to extend its life. In the commented code-block, I attempt to address it, but the reference is no longer available. I have to loop through the loop again to find the match in order to act on it.

How can I loop through a query looking for a best-match and then act on it once I know it's the best match, without having to loop through to find it again?

use bevy::prelude::*;

struct Person;
struct Name(String);

fn main() {
    App::build()
        .add_default_plugins()
        .add_startup_system(startup.system())
        .add_system(boot.system())
        .run();
}

fn boot(mut query: Query<(&Person, &mut Name)>) {
    let mut temp_str = String::new();
    let mut empty: Option<&mut Name> = None;
    for (_p, mut n_val) in &mut query.iter() {
        if n_val.0.to_lowercase() > temp_str.to_lowercase() {
            temp_str = n_val.0.clone();
            empty = Some(&mut n_val);
        }
    }
    println!("{}", temp_str);
    if let Some(n) = empty {
        // ...
    }
    // for (_p, mut n_val) in &mut query.iter() {
    //     if n_val.0 == temp_str {
    //         n_val.0 = "a".to_string();
    //     }
    // }
}

fn startup(mut commands: Commands) {
    commands
        .spawn((Person, Name("Gene".to_string())))
        .spawn((Person, Name("Candace".to_string())))
        .spawn((Person, Name("Zany".to_string())))
        .spawn((Person, Name("Sarah".to_string())))
        .spawn((Person, Name("Carl".to_string())))
        .spawn((Person, Name("Robert".to_string())));
}

Cargo.toml:

[package]
name = "sample"
version = "0.1.0"
authors = [""]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bevy = "0.1.3"

Specific error:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:17:33
   |
17 |     for (_p, mut n_val) in &mut query.iter() {
   |                                 ^^^^^^^^^^^-
   |                                 |          |
   |                                 |          temporary value is freed at the end of this statement
   |                                 creates a temporary which is freed while still in use
...
24 |     if let Some(n) = empty {
   |                      ----- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error[E0597]: `n_val` does not live long enough
  --> src/main.rs:20:26
   |
20 |             empty = Some(&mut n_val);
   |                          ^^^^^^^^^^ borrowed value does not live long enough
21 |         }
22 |     }
   |     - `n_val` dropped here while still borrowed
23 |     println!("{}", temp_str);
24 |     if let Some(n) = empty {
   |                      ----- borrow later used here
1

There are 1 best solutions below

2
On BEST ANSWER

You cannot extend the lifetime of a reference, but that is not your issue here, the error says temporary value dropped while borrowed, so you must extend the lifetime of your temporary.

If you wonder what temporary, the compiler also points to that (literally) in the error message: query.iter(). This is a function call, and the returned value is not bound to anything, so the compiler creates a temporary value for that. Then you iterate using a reference to that temporary. When the for loop ends, the temporary is dropped and any reference lifetime to it expires.

The solution is to bind the temporary to a local variable. This way you extend the lifetime of the object to the scope of the variable:

let mut iter = query.iter();
for (_p, n_val) in &mut iter {
    if n_val.0.to_lowercase() > temp_str.to_lowercase() {
        temp_str = n_val.0.clone();
        empty = Some(n_val);
    }
}

PS: I find quite bizarre the pattern of iterating over &mut iter. I would expect the return of iter() to implement Iterator or IntoIterator directly, but it looks like this is not the case.