【问题标题】:How to take ownership of Any:downcast_ref from trait object?如何从特征对象中获取 Any:downcast_ref 的所有权?
【发布时间】:2016-04-27 15:06:01
【问题描述】:

我遇到了与 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&lt;Node&gt; 而不是 Box&lt;TextNode&gt;,所以我必须将其向下转换为 Box&lt;TextNode&gt;

Any::downcast_ref 的返回值是Option&lt;&amp;T&gt;,所以我无法获取向下转换结果的所有权并将其推送到v

====编辑=====

由于我英语不好,所以我的问题很模糊。

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

我真正需要的是一个向量,Vec&lt;Box&lt;Node&gt;&gt;Vec&lt;Box&lt;Any&gt;&gt;,它可以包含TextNodeNumberNodeActionNode,任何类型的节点都可以实现Node 的特征.

每个节点类型都需要实现copy方法,返回Box&lt;Any&gt;,然后向下转换为具体类型即可。但是要复制Vec&lt;Box&lt;Any&gt;&gt;,因为你不知道每个元素的具体类型,你必须一一检查,效率很低。

如果复制方法返回Box&lt;Node&gt;,那么复制Vec&lt;Box&lt;Node&gt;&gt;很简单。但似乎没有办法从 trait 对象中获取具体类型。

【问题讨论】:

标签: rust


【解决方案1】:

解决问题的(更)意识形态方式:

use std::any::Any;

pub trait Nodeable {
    fn as_any(&self) -> &dyn Any;
}

#[derive(Clone, Debug)]
struct TextNode {}

impl Nodeable for TextNode {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn main() {
    let mut v: Vec<Box<dyn Nodeable>> = Vec::new();
    let node = TextNode {};  // or impl TextNode::new

    v.push(Box::new(node));

    // the downcast back to TextNode could be solved like this:
    if let Some(b) = v.pop() {  // only if we have a node…
        let n = (*b).as_any().downcast_ref::<TextNode>().unwrap();  // this is secure *)
        println!("{:?}", n);
    };
}

*) 这是安全的:只允许将 Nodeables 向下转换为已实现 Nodeable 的类型。

【讨论】:

    【解决方案2】:

    如果你控制trait Node,你可以让它返回一个Box&lt;Any&gt;并使用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 中它有替代品。 如果您还没有看过它们,我想确保您考虑这样做:

    枚举

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

    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 几乎是相同的,所有这些函数可能都需要将向下转换添加到新变体中。

    特征对象(不是任何)

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

    playground

    【讨论】:

    • 我编辑了我的帖子,谢谢你,我找到了另一个解决方案。
    • @WangRuiqi 这是一个非常危险的解决方案(transmute 不安全是有原因的)......但只是出于好奇,你为什么要返回一个 trait 对象 Box&lt;Node&gt; 而不是简单的 Box&lt;Self&gt;并避免所有向下转换,例如in this example?
    • 其实我需要另一个结构ListNode,其中包含TextNodeNumberNodeActionNode等向量。如果返回Box&lt;Self&gt;,我不知道要实现@987654346 @
    • 为什么它很危险,因为我已经知道这两种类型可以进行标量转换?
    • @WangRuiqi 建议您阅读transmutes section of the Rustnomicon
    猜你喜欢
    • 1970-01-01
    • 2017-11-21
    • 1970-01-01
    • 2016-02-14
    • 2019-06-16
    • 2011-06-07
    • 1970-01-01
    • 2023-02-23
    相关资源
    最近更新 更多