0
votes

I have some code which returns a trait object of type MyTrait so that it can return one of several different structs. I would like to implement the Display trait for the trait object so that I can print the object, with the details delegated to the various structs as they each need their own custom formatters.

I can achieve this by including a formatting method as part of the MyTrait definition, and then implementing Display for MyTrait and delegating - like this:

trait MyTrait {
    fn is_even(&self) -> bool;
    fn my_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
}

impl fmt::Display for MyTrait {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.my_fmt(f)
    }
}

However, I already have the Display trait implemented for each of the structs which implement MyTrait. This means I end up with two methods for each struct which do the same thing - the fmt() method to satisfy the Display trait directly on the struct, and the my_fmt() method which is called by the code above. This seems clumsy and repetitions. Is there a simpler way to do it?

Here's a complete example program which illustrates the point. It's a little longer than I would have liked (it's based on the answer to my previous question Calling functions which return different types with shared trait and pass to other functions but I couldn't think of a simpler way to illustrate the point. Of course, in this toy example the structs and the fmt functions are very simple, but in my real application they are more complex.

use std::fmt;

trait MyTrait {
    fn is_even(&self) -> bool;
    fn my_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result;
}

struct First {
    v: u8,
}

struct Second {
    v: Vec<u8>,
}

impl MyTrait for First {
    fn is_even(&self) -> bool {
        self.v % 2 == 0
    }

    fn my_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.v)
    }
}

impl MyTrait for Second {
    fn is_even(&self) -> bool {
        self.v[0] % 2 == 0
    }

    fn my_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.v[0])
    }
}

fn make1() -> First {
    First { v: 5 }
}

fn make2() -> Second {
    Second { v: vec![2, 3, 5] }
}

// Implement Display for the structs and for MyTrait
impl fmt::Display for First {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.v)
    }
}

impl fmt::Display for Second {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.v[0])
    }
}

impl fmt::Display for MyTrait {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.my_fmt(f)
    }
}

fn requires_mytrait<T: MyTrait + ?Sized>(v: &&T) {
    println!("{:?}", v.is_even());
}

fn main() {
    for i in 0..2 {
        let v1;
        let v2;
        let v = match i {
            0 => {
                v1 = make1();
                println!("> {}", v1); // Demonstrate that Display
                                      // is implemented directly
                                      // on the type.
                &v1 as &MyTrait
            }
            _ => {
                v2 = make2();
                println!("> {}", v2); // Demonstrate that Display
                                      // is implemented directly
                                      // on the type.
                &v2 as &MyTrait
            }
        };
        requires_mytrait(&v);
        println!("{}", v); // Here I print the trait object
    }
}

Can anyone suggest a simpler, cleaner way to do this?

1

1 Answers

2
votes

You can make Display a supertrait of MyTrait.

trait MyTrait: fmt::Display {
    fn is_even(&self) -> bool;
}

This will make trait objects of MyTrait be Display. This only works if you expect all implementors of MyTrait to implement Display, but that was also the case in your previous solution.