rust 在选项字段中向上转换trait

yftpprvb  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(78)

我刚刚学习Rust,在选项中向上转换trait ref时遇到了一些问题。我想将其作为ref传递以减少开销。
其基本思想如下:我有些东西(children)在代码中的其他地方定义,它们是ChildT类型。我需要master能够访问它们的属性并操作它们。还有对象,也需要访问孩子-无论是直接访问还是通过master.get_child。当来自C++时,我想在结构体中存储一个指针。这样可以避免复制开销,并且引用沿着所有想要访问各自子对象的对象保持不变
当实现可变性时,它变得更加复杂,我试图用这种方式克服它:

#![allow(unused)]

use num_traits::Num;
use std::{any::Any, rc::Rc};

pub trait AsBase{

    fn as_base(&self) -> &dyn BaseT;
}

impl <T: BaseT + Sized> AsBase for T{
    fn as_base(&self) -> &dyn BaseT{
        self
    }

}

trait BaseT: AsBase{
    fn get_child(&self) -> Option<Rc<&dyn BaseT>>;
    fn get_child_mut(&mut self) -> Option<&mut Rc<&dyn BaseT>>;
}

trait ChildT: BaseT{
    fn child_t_fn(&self);
}

struct C1{
    child: Option<Rc<dyn ChildT>>,
}

impl BaseT for C1{
    fn get_child(&self) -> Option<Rc<&dyn BaseT>> {
        match &self.child{
            Some(x) => return Some(Rc::new(x.as_base()) ),
            None => return None
        }
    }
    fn get_child_mut(&mut self) -> Option<&mut Rc<&dyn BaseT>> {
        match &self.child{
            Some(x) => return Some(&mut Rc::new(x.as_base()) ),
            None => return None
        }
    }
}

impl ChildT for C1{
    fn child_t_fn(&self) {
        
    }
}

// same as C2 code-wise, for the sake of simplicity
struct C2{
    child: Option<Rc<dyn ChildT>>,
}

impl BaseT for C2{
    fn get_child(&self) -> Option<Rc<&dyn BaseT>> {
        match &self.child{
            Some(x) => return Some(Rc::new(x.as_base()) ),
            None => return None
        }
    }
    fn get_child_mut(&mut self) -> Option<&mut Rc<&dyn BaseT>> {
        match &self.child{
            Some(x) => return Some(&mut Rc::new(x.as_base()) ), // <- cannot pass temp. variable
            None => return None
        }
    }
}
impl ChildT for C2{
    fn child_t_fn(&self) {
        
    }
}

// receive two structs, change one child
fn test(c1: &mut Rc<&dyn ChildT>, c2: &mut Rc<&dyn ChildT>){
    let old_child = c2.get_child_mut();

    let mut new_val: Rc<&dyn BaseT> = Rc::new(c1.as_base());
    old_child = Some(new_val); // <- doesnt work. cannot make it mutable (I think because it is temp and would pass to another struct losing scope. how to overcome that?)
}

fn main(){
    // Example use of the upper code
    let c1 = C1{
            child: None
        };
    let mut c2 = C2{
        child: Some(Rc::new(c1))
    };
    let mut c3 = C2{
        child: Some(Rc::new(c2))
    };
    let mut c4 = C1{
        child: Some(Rc::new(c3))
    };
    
    // i just skipped the function .as_child(), it is basically the same as to_base
    test(Rc::new(c4.as_child()),Rc::new(c2.as_child()));

}

字符串

r1wp621o

r1wp621o1#

首先,一个小小的免责声明:Rust的工作原理与其他语言完全不同。Rust声称没有未定义的行为,这导致了许多编译器强制执行的规则,而这些规则在C++中根本不存在。因此,许多在其他语言中工作的编程范例将无法转移到Rust,需要重新思考架构。特别是对于那些在其他语言中已经相当强大的人来说,这可能是一个挑战。
也就是说,通常有非常好的替代方案,最终甚至会使代码更干净,但可能需要一段时间才能理解问题及其潜在的解决方案。
在你的情况下,有几件事需要返工:

  • 一个Rc<&>(引用计数引用)没有意义,因为您现在得到了Rc&的缺点,而没有任何额外的好处。(这是Rc试图避免的主要事情),所以在所有情况下,&Rc<&>更高级上级。所以我假设所有返回Rc<&>的函数实际上都应该返回&
  • Rusts的零未定义行为规则中出现的一个东西是借用检查器;它确保要么有多个引用到一个对象**,要么有**可变性,但永远不会两者都有。你不能有多个可变引用到同一个对象,因为这将非常容易创建未定义的行为。出于这个原因,Rc内部的所有内容都是不可变的-您需要运行时检查以恢复可变性。这可以通过RefCellCellMutexAtomic s来实现,具体取决于用例。在您的情况下,它是一个完整的对象,所以RefCellMutex是唯一有效的选项,而Mutex(线程安全)只有在与Arc而不是Rc配对时才有意义,所以我假设在您的情况下正确的选择是Rc<RefCell<>>
  • 现在我们有一个问题,Rc<RefCell<>>不再允许as获取对我们对象的纯&引用。对RefCell内部的每次访问都需要在运行时计数-这就是为什么refcell的borrow函数返回一个执行运行时引用计数的令牌对象,当你使用&引用时,这个对象需要保持活动状态。这意味着你不能再从你的函数返回&引用。

因此,有多种方法可以继续进行:

  • 将代码中的Rc转换为BoxBoxRc相同,但没有引用计数器--它只是将一个对象放在堆上,这允许您存储dyn对象。事实上,它现在不允许共享,这允许直接从其中获取&引用,甚至&mut引用;尽管你的main(特别是c2)表明共享对你来说是必要的。
  • Rc转换为Rc<RefCell<>>并处理其后果。在您的情况下,这似乎是最有可能的。我们需要解决的下一个问题是如何在test函数中获得修改节点所需的&mut dyn BaseT。可悲的是,这不是一个简单的问题。

所以我很遗憾地认为答案是:你的代码真的需要做这些事情吗?有没有其他方法可以更好地工作?恐怕重新思考你的架构可能是最好的解决方案。很遗憾,我现在无法帮助你,因为真的不清楚你试图用你的代码解决什么实际问题。

相关问题