Замените элемент структуры новым значением, которое использует предыдущее значение - PullRequest
0 голосов
/ 23 сентября 2018

У меня есть структура, которой принадлежит коробочное значение некоторого типа черты.Сама структура также реализует ту же черту.Я хотел бы заменить значение новым экземпляром той же самой структуры, который обернет его.

Следующий код, который не компилируется, должен прояснить, что я пытаюсь сделать:

trait T {}

struct S {
    t: Box<dyn T>,
}
impl T for S {}

impl S {
    fn new(t: Box<dyn T>) -> Self {
        Self { t }
    }

    fn wrap_t(&mut self) {
        self.t = Box::new(Self::new(self.t))
    }
}

Это не удалось:

error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:14:37
   |
14 |         self.t = Box::new(Self::new(self.t))
   |                                     ^^^^ cannot move out of borrowed content

Реализация wrap_t, как это делает компиляция:

use std::mem;

fn wrap_t(&mut self) {
    unsafe {
        let old_t = mem::replace(&mut self.t, mem::uninitialized());
        let new_t = Box::new(Self::new(old_t));
        let uninit = mem::replace(&mut self.t, new_t);
        mem::forget(uninit);
    }
}

Интересно, есть ли безопасный способ сделать это.

1 Ответ

0 голосов
/ 23 сентября 2018

Единственная используемая вами функция unsafe - mem::uninitialized.Вам нужно что-то передать на mem::replace, но реализация Default не сработает, потому что default() возвращает Self, что не дает возможности быть объектно-безопасным.Аналогично, вы не можете реализовать Clone для дублирования старого значения, так как clone() также возвращает Self.

Вы можете просто реализовать фиктивный тип для этой цели:

struct Dummy;
impl T for Dummy {}

fn wrap_t(&mut self) {
    let old_t = mem::replace(&mut self.t, Box::new(Dummy));
    let new_t = Box::new(Self::new(old_t));
    mem::replace(&mut self.t, new_t);
}

Вам также не понадобится mem::forget здесь и сейчас (я предполагаю, что он был там для предотвращения неопределенного поведения, когда неинициализированная память была отброшена).


Как альтернатива Clone, вы можете бросить свой собственный, который клонирует к Box<dyn T>, избегая наличия Self в сигнатуре метода, поэтому черта сохраняет объект в безопасности:

trait T: Debug {
    fn clone_in_box(&self) -> Box<dyn T>;
}

impl T for S {
    fn clone_in_box(&self) -> Box<dyn T> {
        Box::new(S {
            t: self.t.clone_in_box(),
        })
    }
}
fn wrap_t(&mut self) {
    let cloned = self.clone_in_box();
    let old_t = mem::replace(&mut self.t, cloned);
    let new_t = Box::new(Self::new(old_t));
    mem::replace(&mut self.t, new_t);
}

Существует также альтернативный дизайн, который намного проще понять при чтении кода.Это просто для использования self и возврата нового объекта:

fn wrap_t(self) -> Self {
    Self::new(Box::new(Self::new(self.t)))
}

И вместо этого:

s.wrap_t();

Вы бы сделали:

s = s.wrap_t();
...