String type move problem

I’m running into a borrower error that I don’t quite understand yet, or perhaps it’s the String issue that’s blocking me.

pub fn hash_all_files<P: AsRef<Path>>(path: P, algorithm: Algorithm) -> HashMap<String, String> {
  WalkDir::new(&path)
    .into_iter()
    .filter_map(|entry| entry.ok())
    .filter(|entry| entry.file_type().is_file())
    .map(|entry| entry.path().to_owned())
    .filter_map(|entry| entry.into_os_string().into_string().ok())
    .map(|entry| (entry, hasher::get_file_hash(&entry, algorithm.to_owned())))
    .collect()
}

The second to last line .map(|entry| (entry, hasher::get_file_hash(&entry, ...) is causing the following move error:

error[E0382]: borrow of moved value: `entry`
  --> src/lib.rs:15:48
   |
15 |     .map(|entry| (entry, hasher::get_file_hash(&entry, algorithm.to_owned())))
   |           -----   -----                        ^^^^^^ value borrowed here after move
   |           |       |
   |           |       value moved here
   |           move occurs because `entry` has type `std::string::String`, which does not implement the `Copy` trait

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.

So I understand that I’m moving entry into the first position of the tuple I want returned, and that I can’t reuse that same entry, passing it into my hashing function. String doesn’t implement the Copy trait, so what are my options here? I need the hashmap returned by the function to contain the path of the file being hashed as well as the hash.

I guess this is where languages like C# and Java have abstracted away something that is probably important for me to understand.

This is what I ended up with:

pub fn hash_all_files<P: AsRef<Path>>(path: P, algorithm: Algorithm) -> HashMap<String, String> {
  WalkDir::new(&path)
    .into_iter()
    .filter_map(|entry| entry.ok())
    .filter(|entry| entry.file_type().is_file())
    .map(|entry| entry.path().to_owned())
    .filter_map(|entry| entry.into_os_string().into_string().ok())
    .map(|entry| {
      let b = entry.as_str();
      let file_path: String = String::from(b);
      (
        entry,
        hasher::get_file_hash(&file_path, algorithm.to_owned()),
      )
    })
    .collect()
}

This seems really ugly. Would that be accepted in a production ready pull request?

Would this work?

...
.map(|entry| {
    let hash = hasher::get_file_hash(&entry, algorithm.to_owned());
    (entry, hash)
})
.collect()

The hash’s lifetime shouldn’t depend on the input lifetime, so this should work

1 Like

Yep, that worked. I made that way more complicated than I needed to. I’ve got to practice rust more.

Thanks for the help!

1 Like

No problem!

The problem was that Rust does things in the order that it sees things, so it sees the move of entry before the reference to entry and then complains when you try and reference entry. So moving the reference up shows to Rust that it should happen first.