29

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
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Gyscos
  • 1,772
  • 17
  • 22

4 Answers4

24

Rust 2021 (available from Rust 1.56)

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) {}

Rust 1.51

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) {}

Previous Rust versions

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.
  • If a panic happens in the middle of moving the values out (such as somewhere within the 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.
  • Nothing prevents us from accidentally accessing the newly-invalidated values in the array ourselves.

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);
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you. So there is no safe way to do it from an `Array` alone, but using an `ArrayVec` may be a compromise between that and a `Vec`. – Gyscos Dec 22 '15 at 04:57
  • 1
    Thanks for adding the `ptr::read` solution. It makes the intent more clear than `replace` + `uninitialized` or the alternative I was using, `transmute_copy`. – jbatez Mar 18 '18 at 15:56
  • You should mention that `mem::forget` should be used after writing uninitialized memory to the array. – torkleyy May 15 '18 at 08:10
  • @torkleyy well, both `unsafe` solutions are broken on purpose. `mem::forget` would be correct, but only if **all** the values were successfully moved out. That's what the entire point about arrayvec is for: getting the details right is really hard. – Shepmaster May 15 '18 at 12:51
  • Yeah, I know, that's exactly what I'm doing though. – torkleyy May 15 '18 at 13:27
  • @torkleyy then you are *recreating* arrayvec. The point of my answer is to tell people to use the premade solution. Don't reinvent the wheel. – Shepmaster May 15 '18 at 13:38
10

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));
    }
}
aSpex
  • 4,790
  • 14
  • 25
  • And `Option::take` [is implemented](https://github.com/rust-lang/rust/blob/1.5.0/src/libcore/option.rs#L689-L691) with `mem::replace` ^_^. `Option` has a value that can always be created and that is suitable as a "dummy" or "no-op" value (`None`). – Shepmaster Dec 22 '15 at 14:07
  • 1
    @shepmaster the difference is the lack of unsafe code in your project. – Mahmoud Al-Qudsi Aug 17 '17 at 17:26
  • @MahmoudAl-Qudsi I'm not sure I follow you. **All** the solutions (this one and the two in my answer) use unsafe code, the only difference is where it is. This one is in the standard library. Mine has the inline one and one in a crate. – Shepmaster Aug 17 '17 at 19:18
5

Using the non-lexical lifetimes feature (available since Rust 1.31.0) and a fixed-length slice pattern (available since Rust 1.26.0) you can move out of an array:

#[derive(Debug)]
struct Foo;

fn bar(foo: Foo) {
    println!("{:?}", foo);
}

fn main() {
    let v: [Foo; 3] = [Foo, Foo, Foo];
    let [a, b, c] = v;

    bar(a);
    bar(b);
    bar(c);
}

However, this solution does not scale well if the array is big.

An alternative, if you don't mind the extra allocation, is to box the array and convert it into a Vec:

fn main() {
    let v: [Foo; 3] = [Foo, Foo, Foo];
    let v = Vec::from(Box::new(v) as Box<[_]>);

    for a in v {
        bar(a);
    }
}

If the array is very big, that may be an issue. But then, if the array is very big, you should not be creating it in the stack in the first place!

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • "However, this solution does not scale well if the array is big." Are you referring to scaling in terms of performance or just that it is annoying to write out? – Ibraheem Ahmed Apr 19 '21 at 13:23
1

stable since Rust 1.65
This is the use case the LendingIterator was developed for:

extern crate lending_iterator;
use ::lending_iterator::prelude::*;

// 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 array: [Foo; 3] = [Foo, Foo, Foo];

    let mut iter = array.into_lending_iter();
    while let Some(a) = iter.next() {
        bar(a);
    }
}
Kaplan
  • 2,572
  • 13
  • 14