Impl trait

语法

ImplTraitType : impl TypeParamBounds

ImplTraitTypeOneBound : impl TraitBound

impl Trait 提供了指定实现了特定特征的未命名但具体的类型的方法。它可以出现在两种地方:参数位置(它可以充当函数的匿名类型参数)和返回位置(它可以充当抽象返回类型)。

#![allow(unused)]
fn main() {
trait Trait {}
impl Trait for () {}

// argument position: anonymous type parameter
fn foo(arg: impl Trait) {
}

// return position: abstract return type
fn bar() -> impl Trait {
}
}

匿名类型参数

注意:这通常被称为“参数位置的 impl Trait”。(这里“参数”一词更准确,但在该功能的开发过程中使用了“参数位置的 impl Trait”的说法,并且它保留在实现的某些部分。)

函数可以使用 impl 后跟一组特征边界来声明一个参数具有匿名类型。调用者必须提供一个满足匿名类型参数声明的边界的类型,并且函数只能使用通过匿名类型参数的特征边界可用的方法。

例如,这两种形式几乎是等效的

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

// generic type parameter
fn with_generic_type<T: Trait>(arg: T) {
}

// impl Trait in argument position
fn with_impl_trait(arg: impl Trait) {
}
}

也就是说,参数位置的 impl Trait 是泛型类型参数(如 <T: Trait>)的语法糖,只是该类型是匿名的,并且不会出现在 GenericParams 列表中。

**注意:** 对于函数参数,泛型类型参数和 impl Trait 并不完全等效。使用泛型参数(如 <T: Trait>),调用者可以选择在调用站点使用 GenericArgs 显式指定 T 的泛型参数,例如,foo::<usize>(1)。如果 impl Trait 是*任何*函数参数的类型,则调用者在调用该函数时永远不能提供任何泛型参数。这包括返回类型的泛型参数或任何常量泛型。

因此,将函数签名从其中一个更改为另一个可能会对函数的调用者构成重大更改。

抽象返回类型

注意:这通常被称为“返回位置的 impl Trait”。

函数可以使用 impl Trait 返回抽象返回类型。这些类型代表另一种具体类型,调用者只能使用指定的 Trait 声明的方法。函数的每个可能的返回值都必须解析为相同的具体类型。

返回位置的 impl Trait 允许函数返回一个未装箱的抽象类型。这在使用 闭包 和迭代器时特别有用。例如,闭包具有唯一的、不可写的类型。以前,从函数返回闭包的唯一方法是使用 特征对象

#![allow(unused)]
fn main() {
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}
}

这可能会导致堆分配和动态分派的性能损失。无法完全指定闭包的类型,只能使用 Fn 特征。这意味着特征对象是必需的。但是,使用 impl Trait,可以更简单地编写此代码

#![allow(unused)]
fn main() {
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}
}

这也避免了使用装箱特征对象的缺点。

类似地,迭代器的具体类型可能会变得非常复杂,包含链中所有先前迭代器的类型。返回 impl Iterator 意味着函数仅公开 Iterator 特征作为其返回类型的边界,而不是显式指定所有涉及的其他迭代器类型。

特征和特征实现中的返回位置 impl Trait

特征中的函数也可以使用 impl Trait 作为匿名关联类型的语法。

特征中关联函数的返回类型中的每个 impl Trait 都被解糖为一个匿名关联类型。实现的函数签名中出现的返回类型用于确定关联类型的值。

泛型和返回位置的 impl Trait 之间的区别

在参数位置,impl Trait 在语义上与泛型类型参数非常相似。但是,在返回位置,两者之间存在显著差异。与泛型类型参数不同,使用 impl Trait 时,函数会选择返回类型,而调用者不能选择返回类型。

函数

#![allow(unused)]
fn main() {
trait Trait {}
fn foo<T: Trait>() -> T {
    // ...
panic!()
}
}

允许调用者确定返回类型 T,并且函数返回该类型。

函数

#![allow(unused)]
fn main() {
trait Trait {}
impl Trait for () {}
fn foo() -> impl Trait {
    // ...
}
}

不允许调用者确定返回类型。相反,函数会选择返回类型,但只承诺它将实现 Trait

限制

impl Trait 只能作为非 extern 函数的参数或返回类型出现。它不能是 let 绑定的类型、字段类型,也不能出现在类型别名中。