7

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Wang Ruiqi
  • 804
  • 6
  • 19
  • could you please link the original in Go you're trying to port? I'm not familiar with it – Paolo Falabella Jan 26 '16 at 18:36
  • Hi, Go code is in https://github.com/golang/go/blob/master/src/text/template/parse/node.go, the struct `ListNode` has a field `Nodes [] Node` , it can contains all the node structs that implement `interface Node`, and you can get the concrete type with type assertion http://blog.denevell.org/golang-interface-type-assertions-switch.html, – Wang Ruiqi Jan 27 '16 at 03:16
  • In Rust I would go with the `enum` approach I gave in the answer, as a starting point. [Go does not really have enums](http://stackoverflow.com/questions/14426366/what-is-an-idiomatic-way-of-representing-enums-in-golang), so they insert a `NodeType` to be able to tell if a node is a `PipeNode` or an `ActionNode`. You don't need that in Rust. Also Go [does not have generics and uses interfaces and type switches for most of their use cases](https://golang.org/doc/faq#generics). I'd say to give it a try with enum and come back to ask questions if you're stuck – Paolo Falabella Jan 27 '16 at 08:24

2 Answers2

10

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

Paolo Falabella
  • 24,914
  • 3
  • 72
  • 86
  • I edited my post, thanks to you , I find another solution. – Wang Ruiqi Jan 25 '16 at 14:43
  • @WangRuiqi it's a very dangerous solution (transmute is unsafe for a reason)... But just out of curiosity, why do you want to return a trait object `Box` instead of simply `Box` and avoid all downcasting, like [in this example](http://is.gd/IoZT8l)? – Paolo Falabella Jan 25 '16 at 14:57
  • Actually I need another struct `ListNode` which contains a vector of `TextNode`, `NumberNode`, `ActionNode`, etc. If return `Box`, I have no idea to implement the `ListNode` – Wang Ruiqi Jan 25 '16 at 15:52
  • Why it is danger as I already know these two type could be scalar casted? – Wang Ruiqi Jan 25 '16 at 16:01
  • @WangRuiqi I suggest you have a read to the [transmutes section of the Rustnomicon](https://doc.rust-lang.org/nomicon/transmutes.html) – Paolo Falabella Jan 25 '16 at 16:11
  • Thank you,I made a mistake in my code and I thought it work so I post a sample without check. I deleted my answer. – Wang Ruiqi Jan 26 '16 at 02:53
  • @WangRuiqi since my original answer seems not to address your original need, I tried giving you a couple of alternatives to using Any – Paolo Falabella Jan 26 '16 at 16:02
  • Hi,This disscusion https://users.rust-lang.org/t/trait-objects-with-associated-types/746/21 seem to address my need. https://play.rust-lang.org/?gist=f7137355d1e6eaf6aed9&version=stable – Wang Ruiqi Jan 26 '16 at 16:28
  • Your solution is good, too! I edit my question for more detail about my nedd. – Wang Ruiqi Jan 26 '16 at 16:35
2

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.

Kaplan
  • 2,572
  • 13
  • 14