Rust 通过 RFC conservative impl trait 增加了新的语法 impl Trait,它被用在函数返回值的位置上,表示返回的类型将实现这个 Trait。随后的 RFC expanding impl Trait 更进一步,允许 impl Trait 用在函数参数的位置,表示由调用者决定参数的具体类型,其实就等价于函数的泛型参数。
# impl Trait 作为函数参数
根据 RFC on expanding impl Trait, impl Trait 可以用在函数参数中,作用是作为函数的匿名泛型参数。
Expand
impl Traitto allow use in arguments, where it behaves like an anonymous generic parameter.
也就是说,impl Trait 作为函数参数,和泛型参数是等价的:
|
|
不过,impl Trait和泛型参数有一个不同的地方,impl Trait 作为参数,不能明确指定它的类型:
|
|
除了这个差别,可以认为impl Trait 作为函数参数,和使用泛型参数是等价的。
# impl Trait 作为函数返回值
impl Trait 作为函数的返回值,表示返回的类型将实现这个 Trait。
|
|
在这种情况下,需要注意函数的所有返回路径必须返回完全相同的具体类型。
|
|
可以把函数返回值位置的 impl Trait 替换为泛型吗?
|
|
编译器给的错误信息是,期待返回值的类型是泛型类型 T,却实际却返回了一个具体类型。编译器很智能的给出了使用 impl Iterator<Item = u32>作为返回类型的建议:
|
|
# Universals vs. Existentials
在 RFC on expanding impl Trait 中使用了两个术语,Universal 和 Existential:
- Universal quantification, i.e. “for any type T”, i.e. “caller chooses”. This is how generics work today. When you write
fn foo<T>(t: T), you’re saying that the function will work for any choice ofT, and leaving it to your caller to choose theT.- Existential quantification, i.e. “for some type T”, i.e. “callee chooses”. This is how
impl Traitworks today (which is in return position only). When you writefn foo() -> impl Iterator, you’re saying that the function will produce some typeTthat implementsIterator, but the caller is not allowed to assume anything else about that type.
简单来说:
-
impl Trait用在参数位置是 universal type,也就是泛型类型,它可以是任意类型,由函数的调用者指定具体的类型。 -
impl Trait用在返回值位置是 existential type,它不能是任意类型,而是由函数的实现者指定,一个实现了 Trait 的具体类型。调用者不能对这个类型做任何假设。
也就是说,impl Trait 用在返回位置不是泛型,编译时不需要单态化,抽象类型可以简单地替换为调用代码中的具体类型。
# 在 Trait 中使用 impl Trait
Rust 目前还不支持在 Trait 里使用 impl Trait 做返回值:
|
|
因为 impl Trait 用在返回值位置是 existential type,意味着这个函数将返回一个实现了这个 Trait 的单一类型,而函数定义在 Trait 中,意味着每个实现了 Trait 的类型,都可以让这个函数返回不同类型,对编译器来说这很难处理,因为它需要知道被返回类型的具体大小。
一个简单的解决方法是让函数返回 trait object:
|
|
带有 trait object 的函数不是泛型函数,它只带有单一类型,这个类型就是 trait object 类型。Trait object 本身被实现为胖指针,其中,一个指针指向数据本身,另一个则指向虚函数表(vtable)。
这样定义在 Trait 中的函数,返回的不再是泛型,而是一个单一的 trait object 类型,大小固定(两个指针大小),编译器可以处理。