Reputation: 699
I'm working on a parser that has a bunch of different nodes (~6 node kinds right now, more later) and I'm a bit lost as how to interact with the node items (all nodes implement a Node
trait) so I need to use Box<Node>
everywhere.
ListNode
looks like:
pub struct ListNode {
kind: NodeKind,
position: usize,
pub nodes: Vec<Box<Node>>
}
but I can't derive Clone
because Node
doesn't implement it and whenever I try to get for example the kind of node in a test like so:
#[test]
fn test_plain_string() {
let mut parser = Parser::new("plain_string", "Hello world");
parser.parse();
assert_eq!(1, parser.root.nodes.len());
let ref node = parser.root.nodes[0];
let kind = node.get_kind();
assert_eq!(kind, NodeKind::Text);
}
I get borrowing and sizing errors like so for example:
src/parser.rs:186:20: 186:24 error: cannot move out of borrowed content [E0507]
src/parser.rs:186 let kind = node.get_kind();
^~~~
src/parser.rs:186:20: 186:24 error: cannot move a value of type nodes::Node + 'static: the size of nodes::Node + 'static cannot be statically determined [E0161]
src/parser.rs:186 let kind = node.get_kind();
Something similar to this test is in the tests.
How am I supposed to access trait objects or this approach flawed in Rust?
Is it possible to implement traits to a trait (like Debug
) to a trait or do I have implement Debug
manually for each struct embedding a Node
?
Upvotes: 3
Views: 2576
Reputation: 65657
The error comes from the fact that the get_kind
method on Node
expects self
by value, rather than &self
. Passing self
by value means that the method takes ownership of the object (and therefore drops it and the end of the method), which is not necessary here. In general, you should use &self
by default, then change to &mut self
if you need to mutate the object, or self
if you need to consume the object (e.g. because you need to move one of the object's fields elsewhere and you don't want to clone it).
By the way, I noticed that you implemented ToString
for your structs. However, the documentation for ToString
says:
This trait is automatically implemented for any type which implements the
Display
trait. As such,ToString
shouldn't be implemented directly:Display
should be implemented instead, and you get theToString
implementation for free.
Instead of using a trait, you could also consider using an enum
, especially if the types of nodes are known in advance and you don't need extensibility. Also, if later on you need to determine the type of a node, it'll be more natural to do with an enum
(just do pattern matching).
It appears that you need all of your node types to have a kind
and a position
attribute. Depending on how you're going to use these nodes, you might find it more useful to define a struct with these fields plus an enum for the type-specific fields.
struct Node {
//kind: NodeKind, // redundant!
position: usize,
specific: SpecificNode,
}
enum SpecificNode {
List(Vec<Box<Node>>),
Text(String),
VariableBlock(Box<Node>),
Identifier(String),
Int(i32),
Float(f32),
Bool(bool),
}
Hmm, the SpecificNode
enum looks a lot like your NodeKind
enum, doesn't it? In fact, the kind
field becomes redundant, because you can determine the kind by looking at the variant of the specific
field.
Now, instead of implementing methods separately on each type of node, you only define them once, though each method will usually need to pattern match self
in order to access the data of any variant.
If you have methods that apply only to one particular variant (e.g. a method that only makes sense on a List
), with the enum
approach, you'd be able to invoke it on any type of node, as there are no distinct types for each enum
variant (as of Rust 1.6; there are discussions about introducing this feature). However, your code is likely to work on an abstract Node
most of the time, and you'd have to verify what kind of node you have before calling the method anyway, so you might as well move that logic directly into those methods.
Upvotes: 3