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?
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> {}
.