如何将Rc<RefCell<Box<MyStruct>>>传递给接受Rc<RefCell<Box<dyn MyTrait>>>的函数?

9

我最初在这里提出了这个问题,但它被标记为重复,尽管在我看来只有一部分是重复的,因此我创建了一个更具体的问题:

考虑以下代码:

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()

什么是解决此问题的最佳方法?

1
请注意:你的初始代码只在夜版中有效,因为 Rc<MyTrait>。第二段代码使用了额外的 Box,这只在稳定版本中才是必需的。如果没有 Box,你的代码在夜版中可以正常工作,只需要进行一些小的修改。请编辑你的问题,如果你想要一个稳定版本的解决方案。 - oli_obk
谢谢。我确实使用夜版(抱歉,应该提一下)。移除 Box 似乎是解决我的问题的方法。我认为 RefCell 直接与 Trait 一起工作的能力是最近引入的,这就是为什么最初对我不起作用的原因(当我大约两周前更新我的 Rust 夜版时)。 - Dmitry Uvarov
1个回答

5

(此答案的早期版本基本建议克隆底层结构并将其放入新的Rc<RefCell<Box<MyTrait>>对象中;这在稳定的Rust上是必要的,但自那时以来不久,Rc<RefCell<MyStruct>>将无障碍地强制转换为Rc<RefCell<MyTrait>>。)

删除Box<>包装,您可以自由轻松地将Rc<RefCell<MyStruct>>强制转换为Rc<RefCell<MyTrait>>。回想一下,克隆Rc<T>只会产生另一个Rc<T>,将引用计数增加1,您可以像这样做:

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>>


4
Rc<T> 强制转换为 T: TraitRc<Trait> 在 Nightly 版本中是有可能的。我认为他不想复制实际对象,否则就没有使用 Rc 的理由了。你最后一段话是答案。不要传递 Rc<RefCell<Box<X>>> 这种疯狂的东西,而只需调用 borrow 并传递引用即可。 - oli_obk
1
@ker:是的,Rc 强制转换是可行且安全的(而且必要的,因为 Rc::new 明显不能用于不定大小的 T),因为它只暴露了一个不可变引用;Rc<RefCell<T>> 永远不会被强制转换为 Rc<RefCell<Trait>>,因为 RefCell 允许内部可变性,这可能会导致放入不同类型和大小的对象,从而使 Rc 同时引用不同的对象。因此,它总是需要克隆整个值。 - Chris Morgan
2
不行,就像我在问题的评论中说的那样,Rc<RefCell<T>> -> Rc<RefCell<Trait>> 在夜版中是可以的, 我真的看不出会有什么问题,因为你只能通过强制转换才能创建一个 Rc<RefCell<Trait>> - oli_obk
1
由于在安全代码中无法修改特质对象,因此 RefCell<Trait> 无法被更改为指向实现相同特质的另一个对象,因为我们只获得了一个 &mut Trait,它不允许更改特质对象,只能更改实现该特质的原始对象。 - oli_obk
1
不知怎么的,我总是忘记 &mut Trait 不允许替换底层对象,即使我的语句绕开了为什么这样做没有意义的原因... 叹气 - Chris Morgan
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接