Reputation: 1100
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 static
or 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.
Upvotes: 2
Views: 1345
Reputation: 60127
There are several problems here.
The first is that calling
for f in files { ... }
will take files
by value. This is avoidable by taking a reference instead:
for f in &files { ... }
because (&foo).into_iter()
effectively resolves to foo.iter()
.
The second is that files
must be mut
, and the reference &mut
if you are iterating an iterator. If you had some vector, it would make sense to iterate &my_vector
- you can iterate it without modifying it. However, if you have an iterator itself, the state is kept and updated in the iterator itself.
let mut files = (0..10000).map(|x| x.to_string() + file_ext);
for file in &mut files {
create_hardlink(&file);
}
for file in files {
fs::remove_file(&file);
}
The third is that even if you did these things, since you are using a single iterator, you can only iterate each element once! The second loop will be empty. This is the problem @filmor offers solutions for.
Upvotes: 0
Reputation: 32182
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
Upvotes: 5