rustconstraintstraits

Difference between declarations of trait bounds


I'm building myself a parsing library, and I found a key distinction between two types of declared trait bounds.

Take the following trait:

pub trait Parsable: FromStr<Err: Display>
{}

With this, the following function compiles and works just fine:

fn parse<T>(s: &str) -> Result<T, T::Err>
where
    T: Parsable
{
    s.parse()
}

However, if the Parsable trait is declared in this way:

pub trait Parsable: FromStr
where
    Self::Err: Display
{}

Then the above defined parse function fails to compile.

`<T as FromStr>::Err` doesn't implement `std::fmt::Display`
the trait `std::fmt::Display` is not implemented for `<T as FromStr>::Err`

You have to also explicitly constrain T::Err to Display within the declatation of parse like this:

fn parse<T>(s: &str) -> Result<T, T::Err>
where
    T: Parsable,
    T::Err: Display
{
    s.parse()
}

And only now does it actually compile.

My question is - why? What is the semantic difference between the two trait declarations?


Solution

  • You had me scratching my head for a minute there, as I didn't recognise your first syntax (that which is working):

    pub trait Parsable: FromStr<Err: Display>
    {}
    

    It turns out that specifying bounds in associated type position (like Err: Display above) is a (relatively) new feature—it was stabilised in Rust 1.79. As the blog post states (emphasis added):

    • Supertraits - a bound specified via the new syntax is implied when the trait is used, unlike where clauses. Sample syntax: trait CopyIterator: Iterator<Item: Copy> {}.