Summary

Traits can be aliased with the trait TraitAlias = …; construct.

作る、構成体
Currently, the right hand side is a bound
制限する、結び付けられて
a single
単一の
trait, a combination with + traits and lifetimes. Type parameters
仮引数
and lifetimes can be added
たす
to the trait alias
別名
if needed.

Motivation

First motivation: impl

Sometimes, some traits are defined

定義する
with parameters.
仮引数
For instance:
実例

#![allow(unused)] fn main() { pub trait Foo<T> { // ... } }

It’s not uncommon to do that in generic crates and implement

実装する
them in backend crates, where the T template parameter
仮引数
gets substituted with a backend type.

#![allow(unused)] fn main() { // in the backend crate pub struct Backend; impl Foo<Backend> for i32 { // ... } }

Users who want to use that crate will have to export both the trait Foo from the generic crate and the backend singleton type from the backend crate. Instead, we would like to be able to leave the backend singleton type hidden in the crate. The first shot would be to create a new trait for our backend:

#![allow(unused)] fn main() { pub trait FooBackend: Foo<Backend> { // ... } fn use_foo<A>(_: A) where A: FooBackend {} }

If you try to pass an object that implements

実装する
Foo<Backend>, that won’t work, because it doesn’t implement
実装する
FooBackend. However, we can make it work with the following universal impl:

#![allow(unused)] fn main() { impl<T> FooBackend for T where T: Foo<Backend> {} }

With that, it’s now possible to pass an object that implements

実装する
Foo<Backend> to a function expecting a FooBackend. However, what about impl blocks? What happens if we implement
実装する
only FooBackend? Well, we cannot, because the trait explicitely states that we need to implement
実装する
Foo<Backend>. We hit a problem here. The problem is that even though there’s a compatibility
互換性
at the trait bound
制限する、結び付けられて
level between Foo<Backend> and FooBackend, there’s none at the impl level, so all we’re left with is implementing
実装する
Foo<Backend> that will also provide an implementation
実装
for FooBackend because of the universal implementation
実装
just above.

Second example: ergonomic collections and scrapping boilerplate

Another example is associated

関連付けられた
types. Take
とる
the following trait from tokio:

#![allow(unused)] fn main() { pub trait Service { type Request; type Response; type Error; type Future: Future<Item=Self::Response, Error=Self::Error>; fn call(&self, req: Self::Request) -> Self::Future; } }

It would be nice to be able to create a few aliases

別名
to remove boilerplate for very common combinations of associated
関連付けられた
types with Service.

#![allow(unused)] fn main() { Service<Request = http::Request, Response = http::Response, Error = http::Error>; }

The trait above is a http service trait which only the associated

関連付けられた
type Future is left to be implemented.
実装する
Such an alias
別名
would be very appealing because it would remove copying the whole Service trait into use sites trait bounds,
制限する、結び付けられて
or even trait impls. Scrapping such an annoying boilerplate is a definitive plus to the language
言語
and might be one of the most interesting use case.

Detailed design
設計(する)

Syntax
文法

Declaration
宣言

The syntax

文法
chosen to declare
宣言する
a trait alias
別名
is:

#![allow(unused)] fn main() { trait TraitAlias = Trait; }

Trait aliasing

別名
to combinations of traits is also provided
与える
with the standard + construct:
作る、構成体

#![allow(unused)] fn main() { trait DebugDefault = Debug + Default; }

Optionally, if needed, one can provide a where clause

句、節
to express bounds
制限する、結び付けられて
:

#![allow(unused)] fn main() { trait DebugDefault = Debug where Self: Default; // same as the example above }

Furthermore, it’s possible to use only the where clause

句、節
by leaving the list
リスト、列挙する
of traits empty:
空の

#![allow(unused)] fn main() { trait DebugDefault = where Self: Debug + Default; }

It’s also possible to partially bind associated

関連付けられた
types of the right hand side:

#![allow(unused)] fn main() { trait IntoIntIterator = IntoIterator<Item=i32>; }

This would leave IntoIntIterator with a free parameter

仮引数
being IntoIter, and it should be bind the same way associated
関連付けられた
types are bound
制限する、結び付けられて
with regular
普通の、正規の
traits:

#![allow(unused)] fn main() { fn foo<I>(int_iter: I) where I: IntoIntIterator<IntoIter = std::slice::Iter<i32>> {} }

A trait alias

別名
can be parameterized
仮引数
over types and lifetimes, just like traits themselves:

#![allow(unused)] fn main() { trait LifetimeParametric<'a> = Iterator<Item=Cow<'a, [i32]>>;` trait TypeParametric<T> = Iterator<Item=Cow<'static, [T]>>; }

Specifically,

特に
the grammar
文法
being added
たす
is, in informal notation:
記法

ATTRIBUTE* VISIBILITY? trait IDENTIFIER(<GENERIC_PARAMS>)? = GENERIC_BOUNDS (where PREDICATES)?;

GENERIC_BOUNDS is a list

リスト、列挙する
of zero or more traits and lifetimes separated
分割する
by +, the same as the current syntax
文法
for bounds
制限する、結び付けられて
on a type parameter,
仮引数
and PREDICATES is a comma-separated list
リスト、列挙する
of zero or more predicates, just like any other where clause.
句、節
GENERIC_PARAMS is a comma-separated list
リスト、列挙する
of zero or more lifetime and type parameters,
仮引数
with optional
必須でない
bounds,
制限する、結び付けられて
just like other generic definitions.
定義

Use semantics

You cannot directly

直接
impl a trait alias,
別名
but you can have them as bounds
制限する、結び付けられて
, trait objects and impl Trait.


It is an error to attempt to override a previously specified

特定する、指定する、規定する
equivalence constraint
制約
with a non-equivalent type. For example:

#![allow(unused)] fn main() { trait SharableIterator = Iterator + Sync; trait IntIterator = Iterator<Item=i32>; fn quux1<T: SharableIterator<Item=f64>>(...) { ... } // ok fn quux2<T: IntIterator<Item=i32>>(...) { ... } // ok (perhaps subject to lint warning) fn quux3<T: IntIterator<Item=f64>>(...) { ... } // ERROR: `Item` already constrained trait FloIterator = IntIterator<Item=f64>; // ERROR: `Item` already constrained }

When using a trait alias

別名
as a trait object, it is subject to object safety restrictions
制限
after substituting the aliased traits. This means:

  1. it contains
    含む
    an object safe trait, optionally a lifetime, and zero or more of these other bounds:
    制限する、結び付けられて
    Send, Sync (that is, trait Show = Display + Debug; would not be object safe);
  2. all the associated
    関連付けられた
    types of the trait need to be specified;
    特定する、指定する、規定する
  3. the where clause,
    句、節
    if present,
    ある
    only contains
    含む
    bounds
    制限する、結び付けられて
    on Self.

Some examples:

#![allow(unused)] fn main() { trait Sink = Sync; trait ShareableIterator = Iterator + Sync; trait PrintableIterator = Iterator<Item=i32> + Display; trait IntIterator = Iterator<Item=i32>; fn foo1<T: ShareableIterator>(...) { ... } // ok fn foo2<T: ShareableIterator<Item=i32>>(...) { ... } // ok fn bar1(x: Box<ShareableIterator>) { ... } // ERROR: associated type not specified fn bar2(x: Box<ShareableIterator<Item=i32>>) { ... } // ok fn bar3(x: Box<PrintableIterator>) { ... } // ERROR: too many traits (*) fn bar4(x: Box<IntIterator + Sink + 'static>) { ... } // ok (*) }

The lines marked with (*) assume

仮定する
that #24010 is fixed.

Ambiguous constraints
制約

If there are multiple

複数の
associated
関連付けられた
types with the same name in a trait alias,
別名
then it is a static
静的な
error ("ambiguous associated
関連付けられた
type") to attempt to constrain that associated
関連付けられた
type via the trait alias.
別名
For example:

#![allow(unused)] fn main() { trait Foo { type Assoc; } trait Bar { type Assoc; } // same name! // This works: trait FooBar1 = Foo<Assoc = String> + Bar<Assoc = i32>; // This does not work: trait FooBar2 = Foo + Bar; fn badness<T: FooBar2<Assoc = String>>() { } // ERROR: ambiguous associated type // Here are ways to workaround the above error: fn better1<T: FooBar2 + Foo<Assoc = String>>() { } // (leaves Bar::Assoc unconstrained) fn better2<T: FooBar2 + Foo<Assoc = String> + Bar<Assoc = i32>>() { } // constrains both }

Teaching

Traits are obviously a huge prerequisite. Traits aliases

別名
could be introduced at the end of that chapter.

Conceptually, a trait alias

別名
is a syntax
文法
shortcut used to reason about one or more trait(s). Inherently, the trait alias
別名
is usable in a limited set
セットする、集合
of places:

  • as a bound
    制限する、結び付けられて
    : exactly
    正確に
    like a trait, a trait alias
    別名
    can be used to constraint
    制約
    a type (type parameters
    仮引数
    list,
    リスト、列挙する
    where-clause)
  • as a trait object: same thing as with a trait, a trait alias
    別名
    can be used as a trait object if it fits object safety restrictions
    制限
    (see above in the semantics section)
  • in an impl Trait

Examples should be showed for all of the three cases above:

As a bound
制限する、結び付けられて

#![allow(unused)] fn main() { trait StringIterator = Iterator<Item=String>; fn iterate<SI>(si: SI) where SI: StringIterator {} // used as bound }

As a trait object

#![allow(unused)] fn main() { fn iterate_object(si: &StringIterator) {} // used as trait object }

In an impl Trait

#![allow(unused)] fn main() { fn string_iterator_debug() -> impl Debug + StringIterator {} // used in an impl Trait }

As shown above, a trait alias

別名
can substitute associated
関連付けられた
types. It doesn’t have to substitute them all. In that case, the trait alias
別名
is left incomplete and you have to pass it the associated
関連付けられた
types that are left. Example with the tokio case:

#![allow(unused)] fn main() { pub trait Service { type Request; type Response; type Error; type Future: Future<Item=Self::Response, Error=Self::Error>; fn call(&self, req: Self::Request) -> Self::Future; } trait HttpService = Service<Request = http::Request, Response = http::Response, Error = http::Error>; trait MyHttpService = HttpService<Future = MyFuture>; // assume MyFuture exists and fulfills the rules to be used in here }

Drawbacks

  • Adds another construct

    作る、構成体
    to the language.
    言語

  • The syntax

    文法
    trait TraitAlias = Trait requires lookahead in the parser to disambiguate a trait from a trait alias.
    別名

Alternatives
代わりのもの、選択肢

Should we use type as the keyword instead of trait?

type Foo = Bar; already creates an alias

別名
Foo that can be used as a trait object.

If we used type for the keyword, this would imply that Foo could also be used as a bound

制限する、結び付けられて
as well. If we use trait as proposed in the body of the RFC, then type Foo = Bar; and trait Foo = Bar; both create an alias
別名
for the object type, but only the latter creates an alias
別名
that can be used as a bound,
制限する、結び付けられて
which is a confusing bit of redundancy.

However, this mixes the concepts of types and traits, which are different, and allows

許可する、可能にする
nonsense like type Foo = Rc<i32> + f32; to parse.
構文解析する

Supertraits & universal impl

It’s possible to create a new trait that derives the trait to alias,

別名
and provide a universal impl

#![allow(unused)] fn main() { trait Foo {} trait FooFakeAlias: Foo {} impl<T> Foo for T where T: FooFakeAlias {} }

This works for trait objects and trait bounds

制限する、結び付けられて
only. You cannot implement
実装する
FooFakeAlias directly
直接
because you need to implement
実装する
Foo first hence, you don’t really need FooFakeAlias if you can implement
実装する
Foo.

There’s currently no alternative

代わりのもの、選択肢
to the impl problem described
記述する
here.

ConstraintKinds

Similar

似ている、同様の
to GHC’s ContraintKinds, we could declare
宣言する
an entire predicate as a reified list
リスト、列挙する
of constraints,
制約
instead of creating an alias
別名
for a set
セットする、集合
of supertraits and predicates. Syntax
文法
would be something like constraint
制約
Foo<T> = T: Bar, Vec<T>: Baz;
, used as fn quux<T>(...) where Foo<T> { ... } (i.e. direct substitution). Trait object usage is unclear.

Syntax
文法
for sole where clause.
句、節

The current RFC specifies

特定する、指定する、規定する
that it is possible to use only the where clause
句、節
by leaving the list
リスト、列挙する
of traits empty:
空の

#![allow(unused)] fn main() { trait DebugDefault = where Self: Debug + Default; }

This is one of many syntaxes

文法
that are available for this construct.
作る、構成体
Alternatives
代わりのもの、選択肢
include:

Unresolved questions

Trait alias
別名
containing
含む
only lifetimes

This is annoying. Consider:

考える、みなす

#![allow(unused)] fn main() { trait Static = 'static; fn foo<T>(t: T) where T: Static {} }

Such an alias

別名
is legit. However, I feel concerned about the actual
実際の
meaning of the declaration
宣言
i.e. using the trait keyword to define
定義する
alias
別名
on lifetimes seems a wrong design
設計(する)
choice and seems not very consistent.

If we chose another keyword, like constraint

制約
, I feel less concerned and it would open further
さらなる、それ以上
opportunies see the ConstraintKinds alternative
代わりのもの、選択肢
discussion above.

Which bounds
制限する、結び付けられて
need to be repeated when using a trait alias?

RFC 1927 intends to change the rules here for traits, and we likely want to have the rules for trait aliases

別名
be the same to avoid
避ける、回避する
confusion.

The constraint

制約
alternative
代わりのもの、選択肢
sidesteps this issue.

What about bounds
制限する、結び付けられて
on type variable
変数、ストレージ
declaration
宣言
in the trait alias?

#![allow(unused)] fn main() { trait Foo<T: Bar> = PartialEq<T>; }

PartialEq has no super-trait Bar, but we’re adding

たす
one via our trait alias.
別名
What is the behavior
ふるまい
of such a feature? One possible desugaring is:

#![allow(unused)] fn main() { trait Foo<T> = where Self: PartialEq<T>, T: Bar; }

Issue 21903 explains the same problem for type aliasing.

別名

Note: what about the following proposal below?

When using a trait alias

別名
as a bound,
制限する、結び付けられて
you cannot add extra bound
制限する、結び付けられて
on the input parameters,
仮引数
like in the following:

#![allow(unused)] fn main() { trait Foo<T: Bar> = PartialEq<T>; }

Here, T adds a Bar bound.

制限する、結び付けられて
Now consider:
考える、みなす

#![allow(unused)] fn main() { trait Bar<T> = PartialEq<T: Bar>; }

Currently, we don’t have a proper understanding of that situation, because we’re adding

たす
in both cases a bound,
制限する、結び付けられて
and we don’t know how to disambiguate between pre-condition and implication. That is, is that added
たす
Bar bound
制限する、結び付けられて
a constraint
制約
that T must fulfil in order
順序
for the trait alias
別名
to be met, or is it a constraint
制約
the trait alias
別名
itself adds? To disambiguate, consider:
考える、みなす

#![allow(unused)] fn main() { trait BarPrecond<T> where T: Bar = PartialEq<T>; trait BarImplic<T> = PartialEq<T> where T: Bar; trait BarImpossible<T> where T: Bar = PartialEq<T> where T: Bar; }

BarPrecond would require the use-site code to fulfil the constraint,

制約
like the following:

#![allow(unused)] fn main() { fn foo<A, T>() where A: BarPrecond<T>, T: Bar {} }

BarImplic would give us T: Bar:

#![allow(unused)] fn main() { fn foo<A, T>() where A: BarImplic<T> { // T: Bar because given by BarImplic<T> } }

BarImpossible wouldn’t compile because we try to express a pre-condition and an implication for the same bound

制限する、結び付けられて
at the same time. However, it’d be possible to have both a pre-condition and an implication on a parameter:
仮引数

#![allow(unused)] fn main() { trait BarBoth<T> where T: Bar = PartialEq<T> where T: Debug; fn foo<A, T>() where A: BarBoth<T>, T: Bar { // T: Debug because given by BarBoth } }