Reputation: 9465
I'm new to Rust but I'm already familiar with Generics. At least that's what I thought until I came across this trait definition (edited for brevity):
pub trait Queue<Item: ItemT>: Core<Item> {
fn validate(
&self,
hash: <Item as ItemT>::Hash
) -> bool;
}
What's this Item as ItemT
thing? Isn't Item
already of type ItemT
? I'm also perplexed by the syntax <Item as ItemT>::Hash
... what's that supposed to be? Is it the same as ItemT::Hash
? If yes, then why not say so?
Also, incidentally, does the syntax : Core<Item>
mean that the Queue
trait requires all implementors to also implement the Core
trait? Or does it mean something else? And why would you do that? Who cares about Core
? If somebody needs the Core
trait, can't they just say so?
Upvotes: 0
Views: 88
Reputation: 60122
The syntax <Type as Trait>::Item
is the fully-qualified syntax used to explicitly refer to an associated Item
of a Trait
on a Type
. This is not terribly common to write out yourself, but you may need to if there is an ambiguity in what Item
means for a Type
.
Consider this modified example using Self::Hash
where the current trait and supertrait both have a Hash
associated type:
trait Core<Item> {
type Hash;
}
trait Queue<Item>: Core<Item> {
type Hash;
fn validate(
&self,
hash: Self::Hash
) -> bool;
}
error[E0221]: ambiguous associated type `Hash` in bounds of `Self`
--> src/lib.rs:9:15
|
2 | type Hash;
| ---------- ambiguous `Hash` from `Core<Item>`
...
6 | type Hash;
| ---------- ambiguous `Hash` from `Queue<Item>`
...
9 | hash: Self::Hash
| ^^^^^^^^^^ ambiguous associated type `Hash`
|
help: use fully qualified syntax to disambiguate
|
9 | hash: <Self as Queue<Item>>::Hash
| ~~~~~~~~~~~~~~~~~~~~~~~
help: use fully qualified syntax to disambiguate
|
9 | hash: <Self as Core<Item>>::Hash
| ~~~~~~~~~~~~~~~~~~~~~~
In this case, it is ambiguous what Self::Hash
means, so the compiler requires that you fully-qualify what trait to use.
You may also see this syntax used in generated documentation. As an example, take the str::rmatches
function. The original signature is written like so:
pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>
where
P: Pattern<'a, Searcher: ReverseSearcher<'a>>
But it shows up in the documentation like this:
pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>
where
P: Pattern<'a>,
<P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,
Not exactly sure why this is; perhaps the designers thought disambiguation was a good idea by default, it was simpler this way, or if it was just better for generating links. Either way, its something you'll come across so its better to learn it and not get confused later.
In your particular example, I don't see why Item::Hash
would need to be qualified, but perhaps there is more at play than what is shown, could simply be a fluke, or maybe its just an artifact caused by a previous version of the code.
Also, incidentally, does the syntax
: Core<Item>
mean that theQueue
trait requires all implementors to also implement theCore
trait? Or does it mean something else? And why would you do that? Who cares aboutCore
? If somebody needs theCore
trait, can't they just say so?
Yes, this is constraining the Queue<Item>
trait that any implementation must also implement Core<Item>
. Core<Item>
is also called a supertrait with this usage.
This snippet is a bit too reduced to definitively say why that would be desired, but consider something like DoubleEndedIterator
which has Iterator
as a supertrait since it makes no sense for something to be a double-ended iterator but not be an iterator. So its often used in similar scenarios where it makes no sense to use use the subtrait independently.
But your instincts are good; if a trait can reasonably be used without the supertrait, then it shouldn't.
Upvotes: 3
Reputation: 43782
What's this
Item as ItemT
thing?
<Item as ItemT>::
is how you specify associated item (type, method, constant) from the implementation of the trait ItemT
for the type Item
.
For a less complex example, suppose we have some type Foo
that implements the Default
trait. Then the following are all ways of calling the same function:
let a: Foo = Default::default();
let b = Foo::default();
let c = <Foo as Default>::default();
and their particular characteristics are that:
a
depends on having the result type specified to infer which type should be used with the Default trait.b
might be referring to an inherent (non-trait) associated function of Foo
, rather than the one from Default
, if someone wrote
impl Foo { fn default() { ... } }
c
is fully specified.I'm also perplexed by the syntax
<Item as ItemT>::Hash
... what's that supposed to be? Is it the same asItemT::Hash
?
Yes.
If yes, then why not say so?
I don't know the exact logic, but often the compiler requires you to specify which trait (ItemT
) the associated type (Hash
) came from — even if you might think it was obvious. I presume this is in order to avoid future ambiguity if additional traits define associated types that might have the same name Hash
.
does the syntax
: Core<Item>
mean that theQueue
trait requires all implementors to also implement theCore
trait?
Yes, precisely so. This syntax is called supertraits.
And why would you do that? Who cares about
Core
? If somebody needs theCore
trait, can't they just say so?
In some cases, trait bounds would get very verbose if every required trait had to be spelled out for every generic function. But more generally, it's used when the requirement is a logical necessity, “it cannot possibly make sense to implement trait A and not implement trait B”, or at least when that would be very impractical (the library design chooses not to support the cases where B
would not be implemented).
Upvotes: 0