Hi and welcome, I've moved your post to a new topic, because we try to avoid reviving very old topics.
Regarding the questions you pose, I'll try to answer some
The syntax you are referring to only appears in trait bounds, so when there's a trait
trait Trait {
type T;
}
then a generic parameter can be introduced A: Trait<T = XX>
, or a where
clause can contain something like Foo<Bar>: Trait<T = Baz>
.
I don't know of a good concrete name for this kind of syntax off the top of my head either. In order to understand it better, it might be helpful to interpret it as an "abbreviation" for a combination of constraints where
A: Trait<T = XX>
is short for
A: Trait,
A::T == XX,
assuming we could use a ==
-notation for requiring two types to be equal. (Such a notation does however not exist in Rust [yet].)
Despite the fact that <T = XX>
appears inside of angled brackets, it doesn't mean that it should be considered a "generic type parameter" necessarily.
I looked up the RFC that introduced the syntax (as well as introducing associated types in the first place), and it describes it here:
Associated types are not treated as parameters to a trait, but in some cases a function will want to constrain associated types in some way. For example, as explained in the Motivation section, the Iterator
trait should treat the element type as an output:
trait Iterator {
type A;
fn next(&mut self) -> Option<A>;
...
}
For code that works with iterators generically, there is no need to constrain this type:
fn collect_into_vec<I: Iterator>(iter: I) -> Vec<I::A> { ... }
But other code may have requirements for the element type:
- That it implements some traits (bounds).
- That it unifies with a particular type.
These requirements can be imposed via where
clauses:
fn print_iter<I>(iter: I) where I: Iterator, I::A: Show { ... }
fn sum_uints<I>(iter: I) where I: Iterator, I::A = uint { ... }
In addition, there is a shorthand for equality constraints:
fn sum_uints<I: Iterator<A = uint>>(iter: I) { ... }
In general, a trait like:
trait Foo<Input1, Input2> {
type Output1;
type Output2;
lifetime 'a;
const C: bool;
...
}
can be written in a bound like:
T: Foo<I1, I2>
T: Foo<I1, I2, Output1 = O1>
T: Foo<I1, I2, Output2 = O2>
T: Foo<I1, I2, Output1 = O1, Output2 = O2>
T: Foo<I1, I2, Output1 = O1, 'a = 'b, Output2 = O2>
T: Foo<I1, I2, Output1 = O1, 'a = 'b, C = true, Output2 = O2>
The output constraints must come after all input arguments, but can appear in any order.
Note that output constraints are allowed when referencing a trait in a type or a bound, but not in an IMPL_SEGMENT
path:
- As a type:
fn foo(obj: Box<Iterator<A = uint>>
is allowed.
- In a bound:
fn foo<I: Iterator<A = uint>>(iter: I)
is allowed.
- In an
IMPL_SEGMENT
: <I as Iterator<A = uint>>::next
is not allowed.
The reason not to allow output constraints in IMPL_SEGMENT
is that such paths are references to a trait implementation that has already been determined -- it does not make sense to apply additional constraints to the implementation when referencing it.
Output constraints are a handy shorthand when using trait bounds, but they are a necessity for trait objects, which we discuss next.
Notably, associated lifetimes don't seem to have made it into the language after all; but the I: Iterator<A = uint>
syntax is described as a shorthand for I: Iterator, I::A = uint
.
Ah, and there is a name after all, they call it “Output constraints”. I don’t know if that name stuck around and anyone uses that name in particular, but at least it’s some name for the syntax.
And to understand the example of using them “in a type”,
As a type: fn foo(obj: Box<Iterator<A = uint>>
is allowed.
note that this is just old syntax for fn foo(obj: Box<dyn Iterator<Item = usize>>
.
Also feel free to scroll up to the motivation section in that RFC to learn more about why they were introduced in the first place.