Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does this syntax mean (<T=Self>) and when to use it?

Tags:

generics

rust

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:

  • What does it really mean?
  • What variations are possible (maybe something crazy like <T=Self: 'static>)?
  • when is it useful (examples)?
like image 569
Christoph Avatar asked Aug 16 '15 14:08

Christoph


2 Answers

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;
}

Default type parameters

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> { /* ... */ }

Associated types

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.

To sum up

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.

like image 198
Levans Avatar answered Nov 15 '22 07:11

Levans


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.

like image 29
Malcolm Avatar answered Nov 15 '22 07:11

Malcolm