Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to take ownership of Any:downcast_ref from trait object?

Tags:

rust

I've met a conflict with Rust's ownership rules and a trait object downcast. This is a sample:

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 => ()
    };

}

The TextNode::gen method has to return Box<Node> instead of Box<TextNode>, so I have to downcast it to Box<TextNode>.

Any::downcast_ref's return value is Option<&T>, so I can't take ownership of the downcast result and push it to v.

====edit=====

As I am not good at English, my question is vague.

I am implementing (copying may be more precise) the template parser in Go standard library.

What I really need is a vector, Vec<Box<Node>> or Vec<Box<Any>>, which can contain TextNode, NumberNode, ActionNode, any type of node that implements the trait Node can be pushed into it.

Every node type needs to implement the copy method, return Box<Any>, and then downcasting to the concrete type is OK. But to copy Vec<Box<Any>>, as you don't know the concrete type of every element, you have to check one by one, that is really inefficient.

If the copy method returns Box<Node>, then copying Vec<Box<Node>> is simple. But it seems that there is no way to get the concrete type from trait object.

like image 698
Wang Ruiqi Avatar asked Feb 07 '23 23:02

Wang Ruiqi


2 Answers

If you control trait Node you can have it return a Box<Any> and use the Box::downcast method

It would look like this:

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);
    }
}

Generally speaking, you should not jump to using Any. I know it looks familiar when coming from a language with subtype polymorphism and want to recreate a hierarchy of types with some root type (like in this case: you're trying to recreate the TextNode is a Node relationship and create a Vec of Nodes). I did it too and so did many others: I bet the number of SO questions on Any outnumbers the times Any is actually used on crates.io.

While Any does have its uses, in Rust it has alternatives. In case you have not looked at them, I wanted to make sure you considered doing this with:

enums

Given different Node types you can express the "a Node is any of these types" relationship with an enum:

struct TextNode;
struct XmlNode;
struct HtmlNode;

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

With that you can put them all in one Vec and do different things depending on the variant, without downcasting:

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

adding a variant means potentially changing your code in many places: the enum itself and all the functions that do something with the enum (to add the logic for the new variant). But then again, with Any it's mostly the same, all those functions might need to add the downcast to the new variant.

Trait objects (not Any)

You can try putting the actions you'd want to perform on the various types of nodes in the trait, so you don't need to downcast, but just call methods on the trait object. This is essentially what you were doing, except putting the method on the Node trait instead of downcasting.

playground

like image 106
Paolo Falabella Avatar answered Feb 23 '23 14:02

Paolo Falabella


The (more) ideomatic way for the problem:

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);
    };
}

*) This is secure: only Nodeables are allowd to be downcasted to types that had Nodeable implemented.

like image 25
Kaplan Avatar answered Feb 23 '23 14:02

Kaplan