Help understanding moving/borrowing in context of closure

I'm trying to understand what's happening with moving/borrowing here.

fn main() {
    let arg_set: HashSet<&'static str> = ["calvin",
                                          "hobbes"].iter().cloned().collect();
    let args: Vec<String> = env::args().collect();

    if args.len() == 1 {
        panic!("usage: comics calvin|hobbes")
    } else {
        assert!(args.iter().all(|&x| arg_set.contains(x.as_str())));
    }

    println!("Made it to the end!");
}

Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=470217d9343a00615fcc56ca47044f39

The error I'm recieving:

     |         assert!(args.iter().all(|&x| arg_set.contains(x.as_str())));
     |                                  ^-
     |                                  ||
     |                                  |data moved here
     |                                  |move occurs because `x` has type `String`, which does not implement the `Copy` trait
     |                                   help: consider removing the `&`: `x`

Here is my understanding. args.iter() returns an immutable iterator on the vector. Then all() take a mutable reference to the the iterator(?). My understanding really falls apart here. In the closure, I attempt to borrow x which is the iterator that points to String? Or is x a actually a String? I know because String holds data on the heap, Moving is the default. However, my thought with |&x| is that we'd be borrowing String in the closure?

The iterator is producing elements of type &String.

Within the arguments of the closure, &x is a pattern, which is attempting to "unpack" the borrow. Consider this statement:

let &x = &String::new();
let x = &String::new()

observe the symmetry on the first line: on the right hand side, we borrow the string and on the left we match on that borrowed string; x in this case would just be String. On the second line, x would have the type &String.

You should do what the compiler suggested and remove the & from the argument to the closure. x will have type &String which is sufficient to call .as_str() on.

1 Like

@geeklint gave the solution, but here are some more details on what's going on.

args.iter() returns a std::slice::Iter<'_, String> which holds on to an immutable reference into your Vec. When iterating over items, it returns &Strings. You own the Iter; even though it iterates over immutable objects, you can (and have to) mutate the iterator itself in order to iterate. (The iterator needs to update its state.)

The all method takes a mutable reference to the Iter, as it needs to iterate over the items. It calls the provided closure on the items (&String) that gets iterated over. Your closure doesn't see the iterator itself.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.