I have ownership of an array of size 3 and I would like to iterate on it, moving the elements out as I go. Basically, I would like to have IntoIterator
implemented for a fixed-sized array.
Since arrays don't implement this trait in the standard library (I understand why), is there a workaround to get the desired effect? My objects are not Copy
nor Clone
. I'd be okay creating a Vec
from the array and then iterating into the Vec
, but I'm not even sure how to do that.
(For information, I'd like to fulfill an array of Complete
)
Here is a simple example of the situation (with a naive iter()
attempt):
// No-copy, No-clone struct #[derive(Debug)] struct Foo; // A method that needs an owned Foo fn bar(foo: Foo) { println!("{:?}", foo); } fn main() { let v: [Foo; 3] = [Foo, Foo, Foo]; for a in v.iter() { bar(*a); } }
playground
Gives
error[E0507]: cannot move out of borrowed content --> src/main.rs:14:13 | 14 | bar(*a); | ^^ cannot move out of borrowed content
To change the position of an element in an array:Use the splice() method to insert the element at the new index in the array. The splice method changes the original array by removing or replacing existing elements, or adding new elements at a specific index.
Create a temp variable and assign the value of the original position to it. Now, assign the value in the new position to original position. Finally, assign the value in the temp to the new position.
The array can be left rotated by shifting its elements to a position prior to them which can be accomplished by looping through the array and perform the operation arr[j] = arr[j+1]. The first element of the array will be added to the last of rotated array.
You can iterate the array with a for loop:
fn main() { let v: [Foo; 3] = [Foo, Foo, Foo]; for a in v { bar(a); } } struct Foo; fn bar(_: Foo) {}
You can use std::array::IntoIter
to get a by-value array iterator:
use std::array::IntoIter; fn main() { let v: [Foo; 3] = [Foo, Foo, Foo]; for a in IntoIter::new(v) { bar(a); } } struct Foo; fn bar(_: Foo) {}
The core thing you would need is some way of getting the value out of the array without moving it.
This can be done using mem::transmute
to convert the array to an array of mem::MaybeUninit
, then using ptr::read
to leave the value in the array but get an owned value back:
let one = unsafe { let v = mem::transmute::<_, [MaybeUninit<Foo>; 3]>(v); ptr::read(&v[0]).assume_init() }; bar(one);
It's just a matter of doing this a few times in a loop and you are good to go.
There's just one tiny problem: you see that unsafe
? You guessed it; this is totally, horribly broken in the wider case:
MaybeUninit
does nothing when it is dropped; this can lead to memory leaks.bar
function), the array will be in a partially-uninitialized state. This is another (subtle) path where the MaybeUninit
can be dropped, so now we have to know which values the array still owns and which have been moved out. We are responsible for freeing the values we still own and not the others.The right solution is to track how many of the values in the array are valid / invalid. When the array is dropped, you can drop the remaining valid items and ignore the invalid ones. It'd also be really nice if we could make this work for arrays of different sizes...
Which is where arrayvec comes in. It doesn't have the exact same implementation (because it's smarter), but it does have the same semantics:
use arrayvec::ArrayVec; // 0.5.2 #[derive(Debug)] struct Foo; fn bar(foo: Foo) { println!("{:?}", foo) } fn main() { let v = ArrayVec::from([Foo, Foo, Foo]); for f in v { bar(f); } }
You may use array of Option<Foo>
instead array of Foo
. It has some memory penalty of course. Function take()
replaces value in array with None
.
#[derive(Debug)] struct Foo; // A method that needs an owned Foo fn bar(foo: Foo) { println!("{:?}", foo); } fn main() { let mut v = [Some(Foo),Some(Foo),Some(Foo)]; for a in &mut v { a.take().map(|x| bar(x)); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With