首页 文章

如何从特征对象获取Any:downcast_ref的所有权?

提问于
浏览
3

我遇到了与Rust的所有权规则和特征对象垂头丧气的冲突 . 这是一个示例:

use std::any::Any;
trait Node{
    fn gen(&self) -> Box<Node>;
}

struct TextNode;
impl Node for TextNode{
    fn gen(&self) -> Box<Node>{
        Box::new(TextNode)
    }
}

fn main(){
    let mut v: Vec<TextNode> = Vec::new();
    let node = TextNode.gen();
    let foo = &node as &Any;
    match foo.downcast_ref::<TextNode>(){
        Some(n) => {
            v.push(*n);
        },
        None => ()
    };

}

TextNode::gen 方法必须返回 Box<Node> 而不是 Box<TextNode> ,所以我必须将它向下转换为 Box<TextNode> .

Any::downcast_ref 的返回值是 Option<&T> ,所以我无法获得向下转换结果的所有权并将其推送到 v .

====编辑=====

由于我不擅长英语,我的问题很模糊 .

I am implementing(复制可能更精确)Go标准库中的模板解析器 .

我真正需要的是一个向量, Vec<Box<Node>>Vec<Box<Any>> ,它可以包含 TextNodeNumberNodeActionNode ,可以将任何类型的实现特征的节点 Node 推入其中 .

每个节点类型都需要实现 copy 方法,返回 Box<Any> ,然后向下转换为具体类型即可 . 但要复制 Vec<Box<Any>> ,因为你不知道每个元素的具体类型,你必须逐个检查,这是非常低效的 .

如果复制方法返回 Box<Node> ,则复制 Vec<Box<Node>> 很简单 . 但似乎没有办法从特质对象中获取具体类型 .

1 回答

  • 6

    如果您控制 trait Node ,您可以让它返回 Box<Any> 并使用Box::downcast方法

    它看起来像这样:

    use std::any::Any;
    trait Node {
        fn gen(&self) -> Box<Any>; // downcast works on Box<Any>
    }
    
    struct TextNode;
    
    impl Node for TextNode {
        fn gen(&self) -> Box<Any> {
            Box::new(TextNode)
        }
    }
    
    fn main() {
        let mut v: Vec<TextNode> = Vec::new();
        let node = TextNode.gen();
    
        if let Ok(n) = node.downcast::<TextNode>() {
            v.push(*n);
        }
    }
    

    一般来说,你不应该跳到使用 Any . 我知道它来自具有子类型多态性的语言并且想要重新创建具有某种根类型的类型的层次结构时看起来很熟悉(例如:在这种情况下:您正在尝试重新创建 TextNode is a Node 关系并创建一个节点的 Vec ) . 我做了也做了很多其他人:我打赌 Any 上的SO问题的数量超过了crates.io上实际使用的时间 Any .

    虽然 Any 确实有其用途,但在Rust中它有其他选择 . 如果你没有看过它们,我想确保你考虑这样做:

    枚举

    给定不同的Node类型,您可以使用枚举表达“节点是这些类型中的任何一种”关系:

    struct TextNode;
    struct XmlNode;
    struct HtmlNode;
    
    enum Node {
        Text(TextNode),
        Xml(XmlNode),
        Html(HtmlNode),
    }
    

    有了这个,您可以将它们全部放在一个 Vec 中,并根据变体做不同的事情,而不是向下转换:

    let v: Vec<Node> = vec![
        Node::Text(TextNode),
        Node::Xml(XmlNode),
        Node::Html(HtmlNode)];
    
    for n in &v {
        match n {
            &Node::Text(_) => println!("TextNode"),
            &Node::Xml(_) => println!("XmlNode"),
            &Node::Html(_) => println!("HtmlNode"),
        }
    }
    

    playground

    添加变体意味着可能在许多地方更改您的代码:枚举本身和使用枚举执行某些操作的所有函数(为新变体添加逻辑) . 但话说回来, Any 大致相同,所有这些功能可能需要将向下转换添加到新变种中 .

    特质对象(非任意)

    您可以尝试将您想要执行的操作放在特征中的各种类型的节点上,这样您就不需要向下转换,而只需在特征对象上调用方法 . 这基本上就是你正在做的事情,除了将方法放在Node trait而不是downcast上 .

    playground

相关问题