0

I would like to construct a parse tree of different node types that share functionality via a trait. I need different types because different kinds of nodes might have slightly different types of fields. For example, a branch node like a paragraph does not hold text directly; only via its child Text nodes does it have access to its concrete contents.

With this in mind, I've made the following specifications:

use std::cell::RefCell;
use std::rc::{Rc, Weak};

/// ### Parent
/// A shorthand for an optional (parent might not exist)
/// weak reference to a parent node.
type Parent<T> = Option<Weak<RefCell<T>>>;

/// ### Children
/// Shorthand for a vector of owned child nodes.
/// Empty vector indicates no children.
/// Leaf nodes will assert this.
type Children<T> = Vec<Rc<RefCell<T>>>;

/// ### Document
/// Document root node
pub struct Document<T: Node> {
    id: usize,
    id_counter: NodeId,
    parent: Parent<T>,
    children: Children<T>,
    // -- snip --
}

/// ### trait Node
/// A trait defining functionality for general document tree nodes.
pub trait Node {
    type ID;
    type Parent;
    type Children;

    fn new() -> Self;
}

impl<T: Node> Node for Document<T> {
    type ID = usize;
    type Parent = Parent<T>;
    type Children = Children<T>;

    fn new() -> Self {
        let mut id_counter = NodeId::new();

        Document {
            id: id_counter.assign(),
            id_counter: id_counter,
            parent: None,
            children: Vec::new(),
            // -- snip --
        }
    }
}

/// ### NodeId
/// A global counter of document nodes
#[derive(Debug)]
pub struct NodeId {
    id: usize,
}

impl NodeId {
    /// ### new
    /// A NodeId constructor. In the beginning,
    /// there are 0 Nodes.
    pub fn new() -> Self {
        NodeId { id: 0 }
    }

    /// ### increment
    /// Increments the `NodeId` counter by 1.
    pub fn increment(&mut self) {
        self.id += 1;
    }

    /// ### get
    /// Return a copy of the NodeId counter.NodeId
    pub fn assign(&mut self) -> usize {
        let current = self.id;
        self.increment();
        current
    }
}

However, Rust will not let me construct a new empty document tree in a test. The code

#[test]
fn new_document_node() {
    let doc = Document::new();

    assert_eq!(0, doc.id);
}

results in the error

error[E0282]: type annotations needed for `Document<T>`
  --> src/lib.rs:85:15
   |
85 |     let doc = Document::new();
   |         ---   ^^^^^^^^^^^^^ cannot infer type for type parameter `T`
   |         |
   |         consider giving `doc` the explicit type `Document<T>`, where the type parameter `T` is specified

Adding a type annotation does not help:

let doc: Document<T: Node> = Document::new();

results in

error[E0658]: associated type bounds are unstable
  --> src/lib.rs:85:23
   |
85 |     let doc: Document<T: Node> = Document::new();
   |                       ^^^^^^^
   |
   = note: see issue #52662 <https://github.com/rust-lang/rust/issues/52662> for more information
   ^^^^^^^^^^^^^^^^^^^^^

error[E0107]: wrong number of type arguments: expected 1, found 0
  --> src/lib.rs:85:14
   |
85 |     let doc: Document<T: Node> = Document::new();
   |              ^^^^^^^^^^^^^^^^^ expected 1 type argument

error[E0229]: associated type bindings are not allowed here
  --> src/lib.rs:85:23
   |
85 |     let doc: Document<T: Node> = Document::new();
   |                       ^^^^^^^ associated type not allowed here

Is it possible to create a tree of objects that implement common functionality, maybe slightly differently? For example, my leaf node types have no children, so their get_children() -> Option<Vec<Children>> method would return an empty vector.

Is this at all achievable at least somewhat ergonomically in Rust?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
sesodesa
  • 1,473
  • 2
  • 15
  • 24
  • You say "trait object", but you don't have one (yet). See [What makes something a “trait object”?](https://stackoverflow.com/q/27567849/155423) and [Using Trait Objects That Allow for Values of Different Types](https://doc.rust-lang.org/book/ch17-02-trait-objects.html). – Shepmaster Jun 08 '20 at 13:16
  • Your current attempt is basically impossible because there's no single type `T` that can be used. – Shepmaster Jun 08 '20 at 13:19
  • @Shepmaster Fair enough. I think I might try the `enum` approach, as I *will* have a fixed set of diffetrent node types. In that case `new` would have a match expression for the different types of nodes, right? – sesodesa Jun 08 '20 at 13:23
  • Yes, I think an enum is a fine choice here as you don't plan on adding new types of nodes from outside of your crate. – Shepmaster Jun 08 '20 at 13:25

0 Answers0