Как переместить значения из массива?

у меня есть владение массивом размера 3, и я хотел бы повторить его, перемещая элементы по мере продвижения. В принципе, я хотел бы иметь IntoIterator реализовано для массива фиксированного размера.

поскольку массивы не реализуют эту особенность в стандартной библиотеке (я понимаю, почему), есть ли обходной путь, чтобы получить желаемый эффект? Мои объекты не Copy, ни Clone. Я был бы в порядке, создав Vec из массива, а затем повторяя в Vec, но я даже не уверен как это сделать.

(для информации, я хотел бы исполнить массив Complete)

вот простой пример ситуации (с наивным iter() попытка):

// 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);
    }
}

детская площадка

дает

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:14:13
   |
14 |         bar(*a);
   |             ^^ cannot move out of borrowed content

3 ответов


основная вещь, которая вам понадобится, - это способ получить значение из массива, не перемещая его. Два таких решения:

  1. использовать mem::replace и mem::uninitialized чтобы заменить каждое значение в массиве мусором, возвращая исходное:

    let one = unsafe { mem::replace(&mut v[0], mem::uninitialized()) };
    bar(one);
    
  2. использовать ptr::read оставить значение в массиве, но получить собственное значение назад:

    let two = unsafe { ptr::read(&v[0]) };
    bar(two);
    

это всего лишь вопрос одного из них несколько раз в цикле, и вы хорошо идти.

есть только одна маленькая проблема: вы видите эти unsafe? Вы догадались; это полностью, ужасно сломано в более широком случае:

  • мы оставляем этот массив, полный произвольных битов, которые все равно будут рассматриваться как Foo. В этой case, ничего особенного не происходит, когда этот массив выходит за рамки там нет специального Drop реализация для типа Foo, но если бы это было, это был бы доступ к недопустимой памяти. Плохие новости!
  • вы мог бы использовать mem::forget игнорировать массив и предотвратить его удаление, но...
  • если паника происходит в процессе перемещения значений (например, где-то внутри bar function), массив будет находиться в частично неинициализированном состоянии. Это другой (тонкий) путь, где Drop реализация может быть вызвана, поэтому теперь мы должны знать, какие значения все еще принадлежат массиву и которые были перемещены. Мы несем ответственность за освобождение ценностей, которыми мы все еще владеем, а не других.
  • ничто не мешает нам случайно получить доступ к недавно недействительным значениям в массиве самостоятельно.

правильное решение -трек сколько значений в массиве действительных / недействительных. Когда массив удаляется, вы можете удалить остальные допустимые элементы и игнорировать недопустимые. Вы также должны избегать автоматического деструктора от бега. Было бы также очень хорошо, если бы мы могли сделать эту работу для массивов разных размеров...

где arrayvec приходит. У него нет точно та же реализация (потому что она умнее), но она имеет ту же семантику:

extern crate arrayvec;

use arrayvec::ArrayVec;

#[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);
    }
}

вы можете использовать массив Option<Foo> вместо массива Foo. Конечно, у него есть какой-то штраф памяти. Функция take() заменяет значение в массиве на 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));
    }
}

С помощью не-лексико жизни функции (только в еженощно) и шаблон среза фиксированной длины вы можете выйти из массив:

#![feature(nll)]

#[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);
}

однако это решение не масштабируется хорошо, если массив большой.

альтернативой, если вы не возражаете против дополнительного выделения, является коробка массива и преобразование его в 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);
    }
}

Если массив очень большой, это может быть проблемой. Но тогда, если массив очень большой, вы не должны создавать его в стек в первую очередь!