Search code examples
vectoriteratorrustcloneownership

use of moved value, which is non-copyable [E0382] [E0277]


I have an ownership problem which I don't understand well. Basically I try to create some hardlinks on my file system and to remove them after being created. Therefore I created a range of integers which I map to the actual file names I like to create and destroy. My naive solution looks like this:

use std::fs;

const src_file: &'static str = "a.txt";
const file_ext: &'static str = ".txt";

fn create_hardlink(dest_file: &str) {
    fs::hard_link(&src_file, &dest_file);
}

fn main() {

    let create = (0..10000).map(|x| x.to_string() + file_ext);
    let remove = (0..10000).map(|x| x.to_string() + file_ext);

    for file in create {
        create_hardlink(&file);
    }

    for file in remove {
        fs::remove_file(&file);
    }
}

But what I actually like to accomplish is a solution, where I don't have to repeat my self for creating the static collection with the file-names and can reuse files for a second for-loop:

...

fn main() {

    let files = (0..10000).map(|x| x.to_string() + file_ext);

    for file in files {
        create_hardlink(&file);
    }

    for file in files {
        fs::remove_file(&file);
    }
}

So when I try this the compiler complains, that the second usage of files is not possible,

src/main.rs:20:17: 20:22 error: use of moved value: `files` [E0382]
src/main.rs:20     for file in files {

because files already moved into the first for-loop:

src/main.rs:16:17: 16:22 note: `files` moved here because it has type `core::iter::Map<core::ops::Range<i32>, [closure@src/main.rs:14:36: 14:64]>`, which is non-copyable

after reading the explanation for rustc --explain E0382 I decided to change the code as follows:

...

fn main() {

    let files = Rc::new(RefCell::new((0..10000).map(|x| x.to_string() + file_ext)));

    for file in files.clone() {
        create_hardlink(&file);
    }

    for file in files.clone() {
        fs::remove_file(&file);
    }
}

But this does not work as expected to me:

src/main.rs:16:5: 18:6 error: the trait `core::iter::Iterator` is not implemented for the type `alloc::rc::Rc<core::cell::RefCell<core::iter::Map<core::ops::Range<_>, [closure@src/main.rs:14:53: 14:81]>>>` [E0277]
src/main.rs:16     for file in files.clone() {
src/main.rs:17         create_hardlink(&file);
src/main.rs:18     }
note: in expansion of for loop expansion
src/main.rs:16:5: 18:6 note: expansion site
src/main.rs:16:5: 18:6 help: run `rustc --explain E0277` to see a detailed explanation
src/main.rs:16:5: 18:6 note: `alloc::rc::Rc<core::cell::RefCell<core::iter::Map<core::ops::Range<_>, [closure@src/main.rs:14:53: 14:81]>>>` is not an iterator; maybe try calling `.iter()` or a similar method
src/main.rs:16     for file in files.clone() {
src/main.rs:17         create_hardlink(&file);
src/main.rs:18     }
note: in expansion of for loop expansion
src/main.rs:16:5: 18:6 note: expansion site
src/main.rs:16:5: 18:6 note: required by `core::iter::IntoIterator::into_iter`
src/main.rs:16     for file in files.clone() {
src/main.rs:17         create_hardlink(&file);
src/main.rs:18     }

What can I do? Do I really have to implement the core::iter::Iterator for the type alloc::rc::Rc<core::cell::RefCell<core::iter::Map<core::ops::Range<_> like rustc --explain E0277 is telling me? I hope not...

Is there a simple solution like defining files statically as staticor as const? Or is my approach with mapping a Range non rusty?

Why do I have a type like <core::iter::Map<core::ops::Range<_> and not something like <core::iter::String>?

I hope you can help me out with that and enlighten a bit the Rust ownership principle to a novice like me.


Solution

  • Rust iterators are only forward iterators, as far as I understand, so they can only be iterated once. You can either collect them into a vector or use a function to generate your iterator:

    // 1st option
    let files: Vec<_> = (0..10000).map(|x| x.to_string() + file_ext).collect();
    
    for f in &files { ... } // Borrow `files`
    
    // 2nd option
    let files = || (0..10000).map(|x| x.to_string() + file_ext);
    
    for f in files() { ... } // Call the closure to get an iterator