Approaches to some version of a blanket Display impl for types that impl MyTrait

A search on this topic generates several hits that quickly get me to explaining why

impl<T> fmt::Display for T where T: TraitWithDisplaySupport 

... won't compile. The reason seems to be the less-intuitive need to consider a "forward-looking" view that prevents the mere possibility of conflicting implementations of the std::fmt::Display trait.

Notwithstanding, the expressiveness of the trait system is getting better and better. In that context, some of the responses seem dated relative to the pace of the trait improvements.

I've benefited a great deal from this online community. In that spirit, I thought I would share an amalgamation of what I've learned and "gotten through" the compiler.

With some input and comments, I thought it might serve as a complete inventory of options to this often requested capability.

how to provide display support for the consumer of MyTrait.

I implemented 3 approaches to leverage MyTrait to provide a unified, default Display capacity. Based on what I suspect are gaps in my understanding of GATs and type annotations, useful alternatives beyond what is here may well exist.

fn main() {
    // A implements MyTrait
    let a = A {/*..*/}

    // option 1
    // A implements Display using a MyTrait default display function
    println!("{}", &a);

    // option 2
    // MyTrait uses a "go-between" struct that implements Display
    println!("{}", &a.as_display());

    // option 3
    // Cast A to the MyTrait that implements Display
    println!("{}", &a as &dyn MyTrait);
}

Questions:

  1. Are there other approaches?

  2. Are there ways to improve the ergonomics by implementing other traits that exploit some of the casting and coercing that happen behind the scenes in Rust? (e.g., similar to implementing Deref for the "newtype" pattern to access the capacity of the underlying type?)

  3. Do generic associated types (GATs) provide a way to solve "forward-looking" potential for collisions during monomorphizations (duplicate implementations)?

Another option is a generic struct that implements Display, which you can wrap any A in.

2 Likes

Thank you @alice

I updated the playground with additional feedback from alice.
4 approaches

fn main() {
    // A implements MyTrait
    let a = A {/*..*/}
    // ...
    // option 4
    // wrap the structs with a generic that implements Display
    // implement MyTrait for &A instead of A
    println!("{}", WithDisplay(&a);
}

Q. Is there a more streamlined way to expose the generic struct to the fields specified by the MyTrait?
A. Yes, the original version used the value of A, this updated version uses a borrow ref.

If it contains a reference to the thing, you can just do

println!("{}", WithDisplay(&a));
1 Like

Ah. Yes. I was stuck thinking about traits for values, not borrows. I updated the previous playground accordingly.

You may be able to take inspiration from Path::display.

1 Like

Thank you.

Perfect. That is a version of the approach in Option 2.

I noticed in the now updated Option 4 (the last version) the type for WithDisplay(&a) prints out the equivalent of &A. I would have expected WithDisplay<&A>. That seems to indicate a coercion took place. Is that right?

Nice. Now up to five. The Path::display shifts the focus more to the display capacity vs "a side effect" of implementing the MyTraitWithDisplaySupport that might be inspired for other reasons (that in turn benefit from the Display capacity).

fn main() {
    // A implements MyTrait
    let a = A {/*..*/}
    // ...
    // option 5
    // inspired by std::path::Display
    // A does not implement TraitWithDispalySupport but rather 
    // implements a method that instantiates a go-between struct
    // that does.  
    println!("{}", &a.display();
}
1 Like