Search code examples
rustserde-json

Is it possible to override default trait implementations in Rust?


I haven't wandered the path of advanced traits much, but I'm wondering if it's possible to save re-writing / copying and pasting nine functions by creating a trait which overrides just one or three functions of a more complex trait.

This was some experimenting I did this evening with serde_json's PrettyFormatter where I'd like to create a version of the PrettyFormatter that just changes how a Vec is printed.

I should note that the idea came from this answer which differs in that I'm consuming serde_json and interested in removing code duplication but likely the answer is still "not possible, check the RFC". It seems wasteful to not be able to re-use code already available.

Here's my minimum case that seems to fail:

trait Formatter {
    fn begin_array_value(&self) {
        println!("Formatter called");
    }
    
    fn two(&self) {
        println!("two")
    }
    
    // ... pretend there are a few more functions ...
    
    fn ten(&self) {
        println!("ten")
    }

}

trait PrettyFormatter: Formatter {
    fn begin_array_value(&self) {
        println!("I'm pretty!");
    }
}

struct MyFormatter { }

// This fails:
impl PrettyFormatter for MyFormatter { }
// This works:
//impl Formatter for MyFormatter { }

fn main() {
    let formatter = MyFormatter { };
    formatter.begin_array_value();
}

Specifically, the error is this:

Standard Error

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `MyFormatter: Formatter` is not satisfied
  --> src/main.rs:16:6
   |
8  | trait PrettyFormatter: Formatter {
   |                        --------- required by this bound in `PrettyFormatter`
...
16 | impl PrettyFormatter for MyFormatter { }
   |      ^^^^^^^^^^^^^^^ the trait `Formatter` is not implemented for `MyFormatter`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

I can copy and paste ~320 the lines, but I'm a big fan of writing as little code as possible. If this is somehow possible, I'd want to submit a PR to that crate so others could just work from a PrettyFormatter trait.


Solution

  • No, traits can not override other traits' implementations.

    The syntax trait PrettyFormatter: Formatter { ... } does not imply a inheritance-like relationship. Anything after the : is a constraint, a requirement for a concrete type to meet in order to implement it. Here it means that anything wanting to implement a PrettyFormatter must also implement Formatter. With that in mind, PrettyFormatter::begin_array_value has no relationship to Formatter::begin_array_value.

    You can implement both traits for your MyFormatter struct:

    impl Formatter for MyFormatter { }
    impl PrettyFormatter for MyFormatter { }
    

    But trying to call formatter.begin_array_value() will be met with the error indicating the call is ambiguous:

    error[E0034]: multiple applicable items in scope
      --> src/main.rs:33:15
       |
    33 |     formatter.begin_array_value();
       |               ^^^^^^^^^^^^^^^^^ multiple `begin_array_value` found
       |
    note: candidate #1 is defined in an impl of the trait `Formatter` for the type `MyFormatter`
      --> src/main.rs:2:5
       |
    2  |     fn begin_array_value(&self) {
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    note: candidate #2 is defined in an impl of the trait `PrettyFormatter` for the type `MyFormatter`
      --> src/main.rs:19:5
       |
    19 |     fn begin_array_value(&self) {
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    help: disambiguate the associated function for candidate #1
       |
    33 |     Formatter::begin_array_value(&formatter);
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    help: disambiguate the associated function for candidate #2
       |
    33 |     PrettyFormatter::begin_array_value(&formatter);
       |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    Is there a way to avoid re-implementing the nine other functions for PrettyFormatter?

    You will have to implement them, but you can defer to the Formatter implementation like so:

    trait PrettyFormatter: Formatter {
        fn begin_array_value(&self) {
            Formatter::begin_array_value(self);
        }
    
        fn two(&self) {
            Formatter::two(self);
        }
    
        // ... others
        
        fn ten(&self) {
            Formatter::ten(self);
        }
    }
    

    The ambiguous function call problem will still exist, but only if both traits are in scope. If the original Formatter isn't in scope, then there won't be any issue.

    See the answers here for more info and other solutions.