rusttraits

Why do we need a separate impl for a supertrait


Reading the section on Supertraits in the Advanced Traits chapter in the Rust book. Trying to apply this in a super simple concrete example, there is something I don't understand.

When I define a trait bound/supertrait for some custom trait, I am forced to write the implementation of that supertrait for a specific struct in a separate impl-block.

This example works:

use std::fmt::{Display, Formatter};

pub trait Person: Display {
    fn say_hello(&self);
}

pub struct Student {
    name: &'static str
}

impl Person for Student {
    fn say_hello(&self) {
        println!("Hi, i am {}", self)
    }
}

impl Display for Student {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        return write!(f, "{}", self.name);
    }
}

But it fails, if I try to move the fmt implementation into the impl Person block:

...

impl Person for Student {
    fn say_hello(&self) {
        println!("Hi, i am {}", self)
    }
    
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        return write!(f, "{}", self.name);
    }
}

The compiler complains:

error[E0407]: method `fmt` is not a member of trait `Person`
  --> src/supertrait_question.rs:16:5
   |
16 | /     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
17 | |         return write!(f, "{}", self.name);
18 | |     }
   | |_____^ not a member of trait `Person`

error[E0277]: `Student` doesn't implement `std::fmt::Display`
  --> src/supertrait_question.rs:11:6
   |
11 | impl Person for Student {
   |      ^^^^^^ `Student` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `Student`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
note: required by a bound in `Person`
  --> src/supertrait_question.rs:3:19
   |
3  | pub trait Person: Display {
   |                   ^^^^^^^ required by this bound in `Person`

My question is why?

Since I defined Display to be a supertrait of Person, an implementation of the Person trait requires the Display trait to be implemented as well. A trait is just a collection of methods, so I would expect that all methods of the Person trait can be implemented in the same block.

The statement "method fmt is not a member of trait Person" seems strange because Person has the Display trait bound, which means fmt is a member of Person.

This can be further demonstrated by simply omitting the impl Display for Student block entirely, which causes the compiler to tell me that "the trait std::fmt::Display is not implemented for Student". So it is impossible to implement Person, but not to implement Display.

I know that the answer may just be "that is how Rust works", but I would like to understand the reason.


Solution

  • A trait that declares supertraits is merely a trait with constraints, not an extension of its supertraits’ interface.

    Conceptually, by declaring trait Tr2: Tr1, you only say that if a type T implements Tr2, it must implement Tr1 as well, but traits Tr1 and Tr2 are otherwise completely independent, and each creates a separate namespace for its associated items (methods, associated types, constants, etc.). Any code using T may import and make use of Tr1, or Tr2, both, or neither; but if it uses only one of them, it should not even have to think about the other existing.

    More concretely, by making each trait a separate namespace, Rust avoids the well-known fragile base class problem. Each trait can define its own operations that are guaranteed not to clash with any other if it has not been specifically imported into the namespace. It is therefore entirely possible, though perhaps somewhat ill-advised, to write something like this (playground link):

    mod m {
        pub trait Foo {
            fn xyzzy(&self) { println!("foo!"); }
        }
        
        pub trait Quux: Foo {
            fn xyzzy(&self) { println!("quux!"); }
        }
        
        pub struct X;
        impl Foo for X {}
        impl Quux for X {}
    }
    
    use m::X;
    
    fn main() {
        let x = X;
        { 
            use m::Foo;
            x.xyzzy();  // prints "foo!"
        }
        {
            use m::Quux;
            x.xyzzy();  // prints "quux!"
        }
        {
            use m::Foo;
            use m::Quux;
            // x.xyzzy();  // ambiguous
            Foo::xyzzy(&x);
            Quux::xyzzy(&x);
        }
    }