Как я могу повторно использовать поле, из которого я переместил значение?

У меня есть какой-то некопируемый тип и функция, которая его потребляет и (возможно) производит:

type Foo = Vec<u8>;

fn quux(_: Foo) -> Option<Foo> {
    Some(Vec::new())
}

Теперь рассмотрим тип, который чем-то концептуально очень похож на Box:

struct NotBox<T> {
    contents: T
}

Мы можем написать функцию, которая временно перемещает содержимое NotBox и вставляет что-то обратно, прежде чем вернуть его:

fn bar(mut notbox: NotBox<Foo>) -> Option<NotBox<Foo>> {
    let foo = notbox.contents; // now `notbox` is "empty"
    match quux(foo) {
        Some(new_foo) => {
            notbox.contents = new_foo; // we put something back in
            Some(notbox)
        }
        None => None
    }
}

Я хочу написать аналогичную функцию, работающую с Boxes, но компилятору это не нравится:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
    let foo = *abox; // now `abox` is "empty"
    match quux(foo) {
        Some(new_foo) => {
            *abox = new_foo; // error: use of moved value: `abox`
            Some(abox)
        }
        None => None
    }
}

Вместо этого я мог бы вернуть Some(Box::new(new_foo)), но это приводит к ненужному распределению — в моем распоряжении уже есть некоторая память! Можно ли этого избежать?

Я также хотел бы избавиться от операторов match, но опять же компилятору это не нравится (даже для версии NotBox):

fn bar(mut notbox: NotBox<Foo>) -> Option<NotBox<Foo>> {
    let foo = notbox.contents;
    quux(foo).map(|new_foo| {
        notbox.contents = new_foo; // error: capture of partially moved value: `notbox`
        notbox
    })
}

Можно ли обойти это?


person Pan Hania    schedule 15.07.2016    source источник
comment
Вы, кажется, задаете больше, чем один вопрос здесь. Похоже, что матч против карты должен быть перемещен в новый вопрос.   -  person Chris Emerson    schedule 15.07.2016
comment
Я собирался ответить на первую часть, но понял, что еще не понимаю, как работает переезд из Box<T>; похоже, это не связано с чертами Deref или DerefMut. Так что тоже жду хорошего ответа!   -  person Chris Emerson    schedule 15.07.2016
comment
@ChrisEmerson Часть совпадения возникла, когда я пытался создать минимальный пример своей проблемы, поэтому я не проводил много исследований. Я просто подумал, что это, вероятно, связано с общим вопросом и тем фактом, что я не понимаю, как работают частичные перемещения, поэтому я оставил это здесь.   -  person Pan Hania    schedule 16.07.2016


Ответы (3)


Итак, выезд из Box — это особый случай... что теперь?

Модуль std::mem представляет ряд безопасных функций для перемещения значений, не проделывая дыр (!) в безопасности памяти Rust. Здесь представляют интерес swap и replace:

pub fn replace<T>(dest: &mut T, src: T) -> T

Что мы можем использовать так:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
    let foo = std::mem::replace(&mut *abox, Foo::default());

    match quux(foo) {
        Some(new_foo) => {
            *abox = new_foo;
            Some(abox)
        }
        None => None
    }
}

Это также помогает в случае map, потому что не заимствует Box:

fn baz(mut abox: Box<Foo>) -> Option<Box<Foo>> {
    let foo = std::mem::replace(&mut *abox, Foo::default());

    quux(foo).map(|new_foo| { *abox = new_foo; abox })
}
person Matthieu M.    schedule 15.07.2016
comment
Недостатком является то, что Foo:default() должен существовать и, надеюсь, будет дешевым в исполнении. Если ни одно из них не верно, то предложение переключиться на Box<Option<Foo>> было бы полезно, так как тогда вы можете вызвать take, а None — это дешевое создание по умолчанию. - person Shepmaster; 15.07.2016
comment
@Shepmaster: Согласен :) Однако я бы предпочел придерживаться представленного сценария и уточнять только, если у ОП есть более конкретные проблемы, иначе я боюсь, что ответ может утопить любого, кто попытается его прочитать. - person Matthieu M.; 15.07.2016

Выход за рамки в компиляторе осуществляется в специальном регистре. Вы можете переместить что-то из них, но вы не можете переместить что-то обратно, потому что акт перемещения также освобождает место. Вы можете сделать что-то глупое с std::ptr::write, std::ptr::read и std::ptr::replace, но это трудно сделать правильно, потому что что-то действительное должно быть внутри Box, когда оно отбрасывается. Я бы рекомендовал просто принять выделение или вместо этого переключиться на Box<Option<Foo>>.

person Thiez    schedule 15.07.2016
comment
Больше всего в Rust меня раздражает то, что некоторые детали вроде этого, по-видимому, не задокументированы вне веток обсуждения или иногда мимоходом в RFC. :-( Между прочим, это упоминается в этом отложенном RFC. - person Chris Emerson; 15.07.2016
comment
@ChrisEmerson Я думаю, что в этом случае специальный корпус Box - это то, что нужно удалить и преобразовать в предлагаемую черту DerefMove. Не нужно показывать слишком много грязного белья и делать так, чтобы оно было широко интернализировано, если оно может стать более многоразовым и универсальным. Обратите внимание, что RFC также был перезапущен. - person Shepmaster; 15.07.2016

Мы можем написать функцию, которая временно перемещает содержимое NotBox и вставляет что-то обратно, прежде чем вернуть его.

Это потому, что вы можете частично выйти из структуры, которую вы берете по значению. Он ведет себя так, как если бы все поля были отдельными переменными. Однако это невозможно, если структура реализует Drop, потому что drop требуется, чтобы вся структура всегда была действительна (в случае паники).

Что касается обходного пути, вы не предоставили достаточно информации, особенно, почему baz нужно принимать Box в качестве аргумента, а quux нельзя? Какие функции принадлежат вам, а какие являются частью API, который вы не можете изменить? Каков настоящий тип Foo? Это большая?

Лучшим обходным решением было бы вообще не использовать Box.

person krdln    schedule 15.07.2016