rustrecursive-datastructures

How to implement fmt::Display on a recursive data structure in Rust


I tried to provide an implementation of the Display trait on the recursive data structure Tree as defined below.

#[derive(Debug)]
struct Tree <'a> {
    node: (&'a str, Vec<Tree<'a>>)
}

impl <'a> Tree<'a> {
   fn new(str: &'a str) -> Tree<'a> {
    Tree {node: (str, Vec::new())}
   }

   pub fn merge_sub (&mut self, sub: Tree<'a>) {
    self.node.1.push(sub); 
   }
}

fn main(){
 
    let ta = Tree::new("a");
    let mut tb = Tree::new("b");
    tb.merge_sub(ta);

    let tc = Tree::new("c");
    let mut td = Tree::new("d");
    td.merge_sub(tc);

    let te = Tree::new("e");
    let mut tf = Tree::new("f");
    tf.merge_sub(te);


    let mut tg = Tree::new("g");
    tg.merge_sub(tb);
    tg.merge_sub(td);
    tg.merge_sub(tf);
    println!("sub tree g: {:#?}.", tg);
    println!("Display: {}.", tg);
}

Hopefully, the println!("Display: {}.", tg); would render in the format like this below :

   |--b--a
   |
g--|--d--c
   |
   |--f--e

The challenge here is that the fmt function and write! macro may not be the best fit for my requirements. Because I need to manipulate the string representation of inductive cases specifically, while these tools provide more general, high-level functionalities.

impl<'a> fmt::Display for Tree<'a>{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result{
        let length = self.node.1.len();
        if length == 0{
            return write!(f, "{}", self.node.0)
        }else{
            for (i,t) in self.node.1.iter().enumerate(){ 
                // How to get recursive show instance of Tree ?
            } 
        }
    }
}

Solution

  • You can use format! and ToString::to_string to convert something that implements Display into a String you can work with, that gives us this simplistic implementation:

            } else {
                let mut sub_nodes = self
                    .node
                    .1
                    .iter()
                    .map(ToString::to_string)
                    .intersperse("\n\n".into())
                    .collect::<String>()
                    .split('\n')
                    .map(ToString::to_string)
                    .collect::<Vec<_>>();
                let len = sub_nodes.len();
                if len == 1 {
                    return write!(f, "{}--{}", self.node.0, sub_nodes[0]);
                }
                for (i, sn) in sub_nodes.iter_mut().enumerate() {
                    if len / 2 == i {
                        *sn = format!("{}--|--{}", self.node.0, sn);
                    } else {
                        if sn == "" {
                            *sn = format!("   |   ");
                        } else {
                            *sn = format!("   |--{}", sn);
                        }
                    }
                }
                write!(f, "{}", sub_nodes.join("\n"))
            }
    

    Note: This code assumes all node identifiers are 1 character wide and that any subtree only contains a linked list, to make this work properly with any trees you have to output the corresponding amount of spaces or dashes and further special case the different possibilities of sn.