Как передать Rc ‹RefCell‹ Box ‹MyStruct› ›› в функцию, принимающую Rc ‹RefCell‹ Box ‹dyn MyTrait ›››?

Первоначально я задавал этот вопрос здесь, но он был отмечен как повторяющийся , хотя, на мой взгляд, он дублирует только его часть, поэтому я создал более конкретный:

Рассмотрим следующий код:

use std::rc::Rc;

trait MyTrait {
    fn trait_func(&self);
}

struct MyStruct1;

impl MyStruct1 {
    fn my_fn(&self) {
        // do something
    }
}

impl MyTrait for MyStruct1 {
    fn trait_func(&self) {
        // do something
    }
}

fn my_trait_fn(t: Rc<dyn MyTrait>) {
    t.trait_func();
}

fn main() {
    let my_str: Rc<MyStruct1> = Rc::new(MyStruct1);
    my_trait_fn(my_str.clone());
    my_str.my_fn();
}

Этот код работает нормально. Теперь я хочу изменить определение trait_func, чтобы принять &mut self, но это не сработает, поскольку Rc работает только с неизменяемыми данными. Решение, которое я использую, - обернуть MyTrait в RefCell:

use std::cell::RefCell;

fn my_trait_fn(t: Rc<RefCell<Box<dyn MyTrait>>>) {
    t.borrow_mut().trait_func();
}

fn main() {
    let my_str: Rc<RefCell<Box<MyStruct1>>> = Rc::new(RefCell::new(Box::new(MyStruct1)));
    my_trait_fn(my_str.clone());
    my_str.my_fn();
}

При компиляции получаю сообщение об ошибке:

error[E0308]: mismatched types
  --> src/main.rs:27:17
   |
27 |     my_trait_fn(my_str.clone());
   |                 ^^^^^^^^^^^^^^ expected trait MyTrait, found struct `MyStruct1`
   |
   = note: expected type `std::rc::Rc<std::cell::RefCell<std::boxed::Box<dyn MyTrait + 'static>>>`
              found type `std::rc::Rc<std::cell::RefCell<std::boxed::Box<MyStruct1>>>`
   = help: here are some functions which might fulfill your needs:
           - .into_inner()

Как лучше всего решить эту проблему?


person Dmitry Uvarov    schedule 16.06.2015    source источник
comment
примечание: ваш исходный код работает только по ночам из-за Rc<MyTrait>, ваш второй код использует дополнительный Box, который необходим только в стабильной версии. Без Box ваш код работает нормально по ночам с небольшими изменениями: is.gd/zQUJqp. Пожалуйста, отредактируйте свой вопрос, если вам нужно решение для стабильной ржавчины.   -  person oli_obk    schedule 16.06.2015
comment
Спасибо. Я действительно работаю с nightly (извините, надо было упомянуть). Удаление Box кажется решением моей проблемы. Я думаю, что возможность RefCell работать напрямую с Trait была недавно представлена, поэтому она изначально не работала для меня (когда я обновлял свой ночной Rust около 2 недель назад).   -  person Dmitry Uvarov    schedule 16.06.2015


Ответы (1)


(Более старая версия этого ответа по существу советовала клонировать базовую структуру и поместить ее в новый объект Rc<RefCell<Box<MyTrait>>; это было необходимо в то время в стабильном Rust, но, поскольку вскоре после этого, Rc<RefCell<MyStruct>> будет принудительно перейти к Rc<RefCell<MyTrait>> без проблема.)

Отбросьте Box<> обертку, и вы сможете свободно и легко принудить Rc<RefCell<MyStruct>> к Rc<RefCell<MyTrait>>. Вспомнив, что клонирование Rc<T> просто создает еще Rc<T>, увеличивая счетчик ссылок на единицу, вы можете сделать что-то вроде этого:

use std::rc::Rc;
use std::cell::RefCell;

trait MyTrait {
    fn trait_func(&self);
}

#[derive(Clone)]
struct MyStruct1;
impl MyStruct1 {
    fn my_fn(&self) {
        // do something
    }
}

impl MyTrait for MyStruct1 {
    fn trait_func(&self) {
        // do something
    }
}

fn my_trait_fn(t: Rc<RefCell<MyTrait>>) {
    t.borrow_mut().trait_func();
}

fn main() {
    // (The type annotation is not necessary here, but helps explain it.
    // If the `my_str.borrow().my_fn()` line was missing, it would actually
    // be of type Rc<RefCell<MyTrait>> instead of Rc<RefCell<MyStruct1>>,
    // essentially doing the coercion one step earlier.)
    let my_str: Rc<RefCell<MyStruct1>> = Rc::new(RefCell::new(MyStruct1));
    my_trait_fn(my_str.clone());
    my_str.borrow().my_fn();
}

Как правило, посмотрите, можете ли вы заставить вещи принимать содержащееся значение по ссылке, в идеале даже в общем - fn my_trait_fn<T: MyTrait>(t: &T) и тому подобное, которое обычно можно назвать my_str.borrow() с автоматическим обращением и разыменованием, заботящимся обо всем остальном, а не обо всем Rc<RefCell<MyTrait>> .

person Chris Morgan    schedule 16.06.2015
comment
принуждение Rc<T> к Rc<Trait> для T: Trait возможно ночью ... Я не думаю, что он хотел копировать реальный объект, иначе нет никакой причины для Rc ... Ваш последний абзац - это ответ ... Дон ' t передать Rc<RefCell<Box<X>>> безумие, а просто вызвать borrow и передать ссылку - person oli_obk; 16.06.2015
comment
@ker: да, Rc принуждение возможно и безопасно (и это было необходимо, Rc::new явно не может работать с T без размера), потому что оно предоставляет только неизменяемую ссылку; Rc<RefCell<T>> никогда не будет приводить к Rc<RefCell<Trait>>, потому что RefCell допускает внутреннюю изменчивость, которая может позволить помещать объект другого типа и размера, что заставит Rc ссылаться на разные объекты одновременно. Таким образом, всегда нужно будет клонировать все значение. - person Chris Morgan; 16.06.2015
comment
Нет, как я уже сказал в своем комментарии к вопросу, Rc<RefCell<T>> - ›Rc<RefCell<Trait>> нормально по ночам, а я не Я действительно понимаю, как это может быть проблемой, поскольку вы не можете создать Rc<RefCell<Trait>>, кроме как посредством принуждения. - person oli_obk; 16.06.2015
comment
поскольку мы не можем изменить объект признака в безопасном коде, RefCell<Trait> нельзя изменить так, чтобы он указывал на другой объект, реализующий ту же черту, потому что мы получаем только &mut Trait, который не позволяет изменять объект признака, только исходный объект, который реализовал черта. - person oli_obk; 16.06.2015
comment
Почему-то мне кажется, что я всегда забываю, что &mut Trait не позволяет заменять лежащий в основе объект, даже когда мои утверждения обходят стороной, почему это не имеет смысла… вздох - person Chris Morgan; 16.06.2015
comment
Спасибо обоим. Как упоминалось в моем предыдущем комментарии, удаление Box является правильным решением и работает только по ночам на данный момент. - person Dmitry Uvarov; 16.06.2015
comment
Интересно, почему между структурами на основе стека и кучей такая большая разница, что приведение RefCell структуры стека работает, но приведение RefCell структуры в штучной упаковке обвиняет. Этот недостаток действительно сужает диапазон возможностей создания повторно используемого кода, особенно когда он достаточно велик для увеличения размера стека. - person snuk182; 02.08.2016