2

If I have a Box<dyn Trait1 + Trait2>, can I return a &dyn Trait1 ?

To give some context, I am trying to implement a (specialised) graph in rust. This SpecialisedGraph needs some standard graph algorithms, and I would like to implement these using the Graph trait that could be shared across several graph types but this code is not compiling:

trait Node {
    //...
}

trait Graph {
    fn get_node(&self, key: &str) -> Option<&dyn Node>;
}

trait SpecialisedNode {
    //...
}

trait SpecialisedGraphNode: SpecialisedNode + Node {}

struct SpecialisedGraph {
    nodes: HashMap<String, Box<dyn SpecialisedGraphNode>>
}

impl Graph for SpecialisedGraph {
    fn get_node(&self, key: &str) -> Option<&dyn Node> {
        match self.nodes.get(key) {
            Some(node) => Some(&(**node)),
            None => None
        }
    }
}

With error:

error[E0308]: mismatched types
  --> src\main.rs:25:32
   |
25 |             Some(node) => Some(&(**node)),
   |                                ^^^^^^^^^ expected trait `Node`, found trait `SpecialisedGraphNode`
   |
   = note: expected reference `&dyn Node`
              found reference `&dyn SpecialisedGraphNode`

EDIT: I have edited the question to reflect the comments

EDIT2: Found the answer to my question using the link Why doesn't Rust support trait object upcasting? provided by Shepmaster.

The updated code below now works, thanks all.

trait AsNode {
    fn as_node(&self) -> &dyn Node;
}

trait Node : AsNode {
    //...
}

impl<T: Node> AsNode for T {
    fn as_node(&self) -> &dyn Node {
        self
    }
}

trait Graph {
    fn get_node(&self, key: &str) -> Option<&dyn Node>;
}

trait SpecialisedNode : Node {
    //...
}

struct SpecialisedGraph {
    nodes: HashMap<String, Box<dyn SpecialisedNode>>
}

impl Graph for SpecialisedGraph {
    fn get_node(&self, key: &str) -> Option<&dyn Node> {
        match self.nodes.get(key) {
            Some(node) => Some(node.as_node()),
            None => None
        }
    }
}
  • 1
    It looks like your question might be answered by the answers of [Can I cast between two traits?](https://stackoverflow.com/q/34419561/155423); [Why doesn't Rust support trait object upcasting?](https://stackoverflow.com/q/28632968/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Feb 10 '20 at 20:50
  • 1
    See also [Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?](https://stackoverflow.com/q/40006219/155423) — returning `&Box` is non-idiomatic. – Shepmaster Feb 10 '20 at 21:09
  • Thanks, I didn't know that `&Box` wasn't idiomatic! My traits are related to each other though, when I have a `SpecialisedGraphNode` I know that it implements `Node` – Pierre Henry Feb 11 '20 at 08:24

1 Answers1

2

You can't. A HashMap<String, Box<dyn SpecialisedGraphNode> is not a HashMap<String, Box<dyn Node>, so you can't form a reference of that type to it. If you do it with unsafe code, you invoke undefined behavior, because those boxes don't have the same data.

I also think your feeling is wrong - at least Java, C++ and C# (to name the most common statically typed OO languages) don't let you do that either. (C# lets you do something similar with IReadOnlyDictionary and covariant generic arguments to interfaces in general. But not for concrete types.)

You probably need to rethink your Graph trait. Does it really have to give access to the nodes as a hash map? Maybe it could simply provide a function to look up a node, and return that as a &dyn Node?

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • Thanks @Sebastian-Redl, that's helpful. I ran into another issue so I just edited my question to reflect your comments – Pierre Henry Feb 11 '20 at 08:26