[SOLVED] Cannot return a value from a method after its been through a for loop?

I have struct with a child struct that has a vector in it: game.requirements.peripherals: Vec<Peripheral>

That game object is meant to be attached to a Bridge struct. All that is pretty strightforward.

The problem is that, in the Bridge::new() method, I construct the game object, and then need to loop through its game.requirements.peripherals, and finally attach the game object to the Bridge and return it.

When I try, I get a

error[E0382]: use of moved value: `game`
  --> src/bridge.rs:62:13
   |
40 |         for required_peripheral in game.requirements.peripherals {
   |                                    ----------------------------- value moved here
...
62 |             game,
   |             ^^^^ value used here after partial move
   |
   = note: move occurs because `game.requirements.peripherals` has type `std::vec::Vec<peripherals::required::RequiredPeripheral>`, which does not implement the `Copy` trait

I think I understand what's happening here. The peripheral is being moved out of game.requirements for the for loop. I would have expected ownership to be returned at the end of the loop.

Here is a more full code sample.

let mut network = Network::new();
let mut atlas = Atlas::new();

for required_peripheral in game.requirements.peripherals {
    // real_peripheral is *not* attached to either atlas or network.
    // Neither is required_peripheral
    // discover() returns a dto, and boot_peripheral() and register() both use that information to create their own representations on the network and atlas objects
    let real_peripheral = network.discover(required_peripheral);
    network.boot_peripheral(real_peripheral);
    atlas.register(real_peripheral);
}

// Return the bridge
RfBridge {
    game,
    atlas,
    network
}

I'd really appreciate the help. I am trying to workup a proof of concept that I can hopefully use to convince my workplace to adopt Rust for a few of our projects.

Does discover() need ownership of its parameter? If not, you can change the function signature to take &Peripheral (or &mut Peripheral) instead of Peripheral. I am not sure what a "dto" is, but I think that is the solution.

Thanks for the quick reply. I changed discover() to take a reference &required_peripheral, but I get the same error. I also changed the other two methods inside that loop to accept references -- none of them need ownership. Lastly, I commented out everything in that loop except the discover() method.

for required_peripheral in game.requirements.peripherals {
    // Discover a peripheral that meets the requirement
    let real_peripheral = network.discover(&required_peripheral);

    // Let that peripheral set itself up (create streams, etc)
    network.boot_peripheral(&real_peripheral);

    // Register that peripheral with the Atlas
    atlas.register(&real_peripheral);
}

And, a simplified discover()

// Yes, `&self` will have to be mutable, but it will *not* need to keep ownership of either peripheral
// It uses some of the data to set other flags on `network`
pub fn discover(&mut self, required_peripheral: &RequiredPeripheral) ->  RealPeripheral{
    RealPeripheral {
        name: required_peripheral.name.clone(),
        version: required_peripheral.version.clone()
    }
}

Most of the time (in my ide) when I do a for loop, I notice that it borrows the element as a reference. In this case, it appears to be taking ownership. Am I right there?

I even get the same error when I comment out the entire loop body.

Aha, I think I got it. I need to add .iter() in the loop to get back references, in addition to what @L0uisc suggested
``
for required_peripheral in game.requirements.peripherals.iter() {

Try

for required_peripheral in &game.requirements.peripherals {

See Playground

The reason is, as far as I understand, (and correct me if I'm wrong) that game.requirements.peripherals is a Vec, and thus an owned variable. When you run a for loop with it, Rust will turn it into an iterator implicitly, but the method used depends on the type of the variable over which you iterate.

If you iterate over an owned variable (which is what you did in your OP), Rust will implicitly call .into_iter() on it to get an iterator which yields owned variables for each of the elements of the collection. Thus the elements of the collection is moved out of the collection and is dropped when the loop exits.

If you iterate over an immutable reference (which is what I did above), Rust will implicitly call iter() on it and yield immutable references to the elements.

If you iterate over a mutable reference, Rust will implicitly call iter_mut() on it and yield an iterator of mutable references to the elements.

So, to summarize:

// implicit turning into an iterator via the IntoIter trait
for required_peripheral in game.requirements.peripherals {

is equivalent to

// explicit conversion into a consuming iterator
for required_peripheral in game.requirements.peripherals.into_iter() {

,

// implicit conversion into an iterator of immutable borrows via the IntoIter trait
for required_peripheral in &game.requirements.peripherals {

is equivalent to

// explicit conversion into an iterator of immutable borrows
for required_peripheral in game.requirements.peripherals.iter() {

and

// implicit conversion to an iterator of mutable borrows via the IntoIter trait
for required_peripheral in &mut game.requirements.peripherals {

is equivalent to

// explicit conversion to an iterator of mutable borrows
for required_peripheral in game.requirements.peripherals.iter_mut() {

So, for your example, I would rather use& than .iter(), because it is shorter, but both are doing the same thing and calling exactly the same methods behind the scenes.

I hope this clears this up! :slightly_smiling_face:

5 Likes

Minimal proof / example:

fn main ()
{
    let v = vec![42, 62];
    for _ in v { break; }
    dbg!(v); // Error, use of moved value
}
2 Likes

Wow, that's some really great explanation. I'm marking this as "solved." For googlers, this article also helped a lot: Understanding Rust Loops | Cloudbees Blog

PS: I really love this community. It's been one of Rust's greatest selling points for me.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.