I sometimes see things like <T=Self>
or T=()
in generic structs/traits. I suspect that this has something to do with default types for the generic type T
. I couldn't find any documentation though.
My questions are:
<T=Self: 'static
>)?This syntax can be used in two situations: default type parameters, and associated types. To see the difference and le use, lets have a look at the Add
trait, used to define the +
operator:
pub trait Add<RHS = Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
Here, the type parameter RHS
has a default value: Self
. That means that, whenever I use this trait, the value of this type parameter will default to Self
if I omit it. For example:
impl Add for Foo { /* ... */ }
is the same as
impl Add<Foo> for Foo { /* ... */ }
likewise,
fn foo<T>(t: T) where T: Add { /* ... */ }
is the same as
fn foo<T>(t: T) where T: Add<T> { /* ... */ }
The trait Add
also has an associated type : Output
. This type is chosen by the implementation of the trait.
The reason behind this is that it would not make any sense to implement Add
with different output types while the input types are the same: once Self
and RHS
are known, the Output
type cannot be chosen.
This construct is also used for iterators for example, when you iterate over a container, you do not get to choose the type of the values generated by the iterator: it is thus an associated type.
It is possible to select a value for an associated type in where
clauses, by using this Foo = Bar
syntax. For example:
fn foo<I>(i: I) where I: Iterator<Item=u8> { /* ... */ }
this function can work on any iterator yielding u8
values.
The syntax Foo=Bar
used in the definition of a generic construct allows you to set a default value for a type parameter, and used in where
clauses it allows you to match on the value of an associated type.
There are no possible variations like Foo = Bar : Baz
or things like that.
This syntax is used together with associated types. Sometimes you want to avoid specifying the variables each time and want them to be specified by the type, therefore you have the syntax as follows:
trait Operation {
type Input: Display;
type Output: Display;
fn do_it(&self, g: Self::Input) -> Self::Output;
}
as opposed to
trait Operation<T: Display, V: Display> {
fn do_it(&self, g: T) -> V;
}
In this case when you declare a function which uses Operation
you can simply write fn take_operation<O: Operation>(operation: O, input: O::Input) -> O::Output
instead of fn take_operation<T: Display, V: Display, O: Operation<T, V>>(operation: O, input: T) -> V
.
In some cases you want to limit the range of types you accept, though. Let's take, for example, the case when you want to limit the types of input to u32
. Normally you would write fn take_operation<V: Display, O: Operation<u32, V>>(operation: O, input: u32) -> V
. With the associated types, however, you will specify the constraint differently: fn take_operation<O: Operation<Input=u32>>(operation: O, input: u32) -> O::Output
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With