Lajos Nagy
Lajos Nagy

Reputation: 9465

Using 'as T' in function type signature

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

Answers (2)

kmdreko
kmdreko

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 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?

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

Kevin Reid
Kevin Reid

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 as ItemT::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 the Queue trait requires all implementors to also implement the Core trait?

Yes, precisely so. This syntax is called supertraits.

And why would you do that? Who cares about Core? If somebody needs the Core 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

Related Questions