特征

语法
特征 :
   unsafe? trait IDENTIFIER  GenericParams? ( : TypeParamBounds? )? WhereClause? {
     InnerAttribute*
     AssociatedItem*
   }

特征 描述了一种类型可以实现的抽象接口。此接口由关联项组成,关联项分为三种类型

特征声明在它所在的模块或代码块的类型命名空间中定义了一个特征。

关联项被定义为特征的成员,位于它们各自的命名空间内。关联类型在类型命名空间中定义。关联常量和关联函数在值命名空间中定义。

所有特征都定义了一个隐式的类型参数 Self,它指代“正在实现此接口的类型”。特征还可以包含额外的类型参数。这些类型参数,包括 Self,可以像通常一样受到其他特征等的约束。

特征通过单独的实现为特定类型实现。

特征函数可以省略函数体,用分号代替。这表示实现必须定义该函数。如果特征函数定义了函数体,则此定义充当任何未覆盖它的实现的默认值。 类似地,关联常量可以省略等号和表达式,以指示实现必须定义常量值。关联类型永远不能定义类型,类型只能在实现中指定。

#![allow(unused)] fn main() { // Examples of associated trait items with and without definitions. trait Example { const CONST_NO_DEFAULT: i32; const CONST_WITH_DEFAULT: i32 = 99; type TypeNoDefault; fn method_without_default(&self); fn method_with_default(&self) {} } }

特征函数不允许是 const

特征约束

泛型项可以使用特征作为其类型参数的约束

泛型特征

可以为特征指定类型参数使其成为泛型特征。 这些类型参数出现在特征名称之后,使用与泛型函数中相同的语法。

#![allow(unused)] fn main() { trait Seq<T> { fn len(&self) -> u32; fn elt_at(&self, n: u32) -> T; fn iter<F>(&self, f: F) where F: Fn(T); } }

Dyn 兼容性

dyn 兼容的特征可以是特征对象的基础特征。 如果一个特征具有以下特性,则它是dyn 兼容的

  • 所有 父特征 也必须是 dyn 兼容的。
  • Sized 不能是 父特征。换句话说,它不能要求 Self: Sized
  • 它不能有任何关联常量。
  • 它不能有任何带有泛型的关联类型。
  • 所有关联函数必须可以从特征对象分发,或者显式地不可分发
    • 可分发的函数必须
      • 没有任何类型参数(尽管允许生命周期参数)。
      • 是一个 方法,除了接收器的类型外,不使用 Self
      • 具有以下类型之一的接收器
      • 没有不透明的返回类型;也就是说,
        • 不是 async fn (它有一个隐藏的 Future 类型)。
        • 没有返回位置 impl Trait 类型 (fn example(&self) -> impl Trait)。
      • 没有 where Self: Sized 约束(Self (即 self) 的接收器类型暗示了这一点)。
    • 显式不可分发的函数需要
      • 具有 where Self: Sized 约束(Self (即 self) 的接收器类型暗示了这一点)。

注意:这个概念以前被称为 对象安全

#![allow(unused)] fn main() { use std::rc::Rc; use std::sync::Arc; use std::pin::Pin; // Examples of dyn compatible methods. trait TraitMethods { fn by_ref(self: &Self) {} fn by_ref_mut(self: &mut Self) {} fn by_box(self: Box<Self>) {} fn by_rc(self: Rc<Self>) {} fn by_arc(self: Arc<Self>) {} fn by_pin(self: Pin<&Self>) {} fn with_lifetime<'a>(self: &'a Self) {} fn nested_pin(self: Pin<Arc<Self>>) {} } struct S; impl TraitMethods for S {} let t: Box<dyn TraitMethods> = Box::new(S); }
#![allow(unused)] fn main() { // This trait is dyn compatible, but these methods cannot be dispatched on a trait object. trait NonDispatchable { // Non-methods cannot be dispatched. fn foo() where Self: Sized {} // Self type isn't known until runtime. fn returns(&self) -> Self where Self: Sized; // `other` may be a different concrete type of the receiver. fn param(&self, other: Self) where Self: Sized {} // Generics are not compatible with vtables. fn typed<T>(&self, x: T) where Self: Sized {} } struct S; impl NonDispatchable for S { fn returns(&self) -> Self where Self: Sized { S } } let obj: Box<dyn NonDispatchable> = Box::new(S); obj.returns(); // ERROR: cannot call with Self return obj.param(S); // ERROR: cannot call with Self parameter obj.typed(1); // ERROR: cannot call with generic type }
#![allow(unused)] fn main() { use std::rc::Rc; // Examples of dyn-incompatible traits. trait DynIncompatible { const CONST: i32 = 1; // ERROR: cannot have associated const fn foo() {} // ERROR: associated function without Sized fn returns(&self) -> Self; // ERROR: Self in return type fn typed<T>(&self, x: T) {} // ERROR: has generic type parameters fn nested(self: Rc<Box<Self>>) {} // ERROR: nested receiver not yet supported } struct S; impl DynIncompatible for S { fn returns(&self) -> Self { S } } let obj: Box<dyn DynIncompatible> = Box::new(S); // ERROR }
#![allow(unused)] fn main() { // `Self: Sized` traits are dyn-incompatible. trait TraitWithSize where Self: Sized {} struct S; impl TraitWithSize for S {} let obj: Box<dyn TraitWithSize> = Box::new(S); // ERROR }
#![allow(unused)] fn main() { // Dyn-incompatible if `Self` is a type argument. trait Super<A> {} trait WithSelf: Super<Self> where Self: Sized {} struct S; impl<A> Super<A> for S {} impl WithSelf for S {} let obj: Box<dyn WithSelf> = Box::new(S); // ERROR: cannot use `Self` type parameter }

父特征

父特征 是指为了让一个类型实现特定特征而必须实现的特征。 此外,任何地方的泛型特征对象受到特征的约束,它都可以访问其父特征的关联项。

父特征通过特征的 Self 类型上的特征约束以及以传递方式在这些特征约束中声明的特征的父特征来声明。 一个特征成为它自己的父特征是错误的。

具有父特征的特征称为其父特征的 子特征

以下是将 Shape 声明为 Circle 的父特征的示例。

#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle : Shape { fn radius(&self) -> f64; } }

以下是相同的示例,只是使用了 where 子句

#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64; } }

下一个示例使用 Shape 中的 area 函数为 radius 提供默认实现。

#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle where Self: Shape { fn radius(&self) -> f64 { // A = pi * r^2 // so algebraically, // r = sqrt(A / pi) (self.area() /std::f64::consts::PI).sqrt() } } }

下一个示例在泛型参数上调用父特征方法。

#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle : Shape { fn radius(&self) -> f64; } fn print_area_and_radius<C: Circle>(c: C) { // Here we call the area method from the supertrait `Shape` of `Circle`. println!("Area: {}", c.area()); println!("Radius: {}", c.radius()); } }

类似地,这是一个在特征对象上调用父特征方法的示例。

#![allow(unused)] fn main() { trait Shape { fn area(&self) -> f64; } trait Circle : Shape { fn radius(&self) -> f64; } struct UnitCircle; impl Shape for UnitCircle { fn area(&self) -> f64 { std::f64::consts::PI } } impl Circle for UnitCircle { fn radius(&self) -> f64 { 1.0 } } let circle = UnitCircle; let circle = Box::new(circle) as Box<dyn Circle>; let nonsense = circle.radius() * circle.area(); }

不安全特征

unsafe 关键字开头的特征项表示实现该特征可能是不安全的。 使用正确实现的不安全特征是安全的。 特征实现也必须以 unsafe 关键字开头。

SyncSend 是不安全特征的示例。

参数模式

没有函数体的函数或方法声明只允许 IDENTIFIER_ 通配符 模式。 目前允许 mut IDENTIFIER,但它已被弃用,将来会成为硬错误。

在 2015 版本中,特征函数或方法参数的模式是可选的

#![allow(unused)] fn main() { // 2015 Edition trait T { fn f(i32); // Parameter identifiers are not required. } }

参数的模式种类限制为以下之一

从 2018 版本开始,函数或方法参数模式不再是可选的。 此外,只要有函数体,就允许所有不可反驳的模式。 如果没有函数体,则上述限制仍然有效。

#![allow(unused)] fn main() { trait T { fn f1((a, b): (i32, i32)) {} fn f2(_: (i32, i32)); // Cannot use tuple pattern without a body. } }

项可见性

特征项在语法上允许 Visibility 注解,但在验证特征时会被拒绝。 这允许在使用的不同上下文中以统一的语法解析项。 例如,一个空的 vis 宏片段说明符可以用于特征项,其中宏规则可以用在允许可见性的其他情况中。

macro_rules! create_method { ($vis:vis $name:ident) => { $vis fn $name(&self) {} }; } trait T1 { // Empty `vis` is allowed. create_method! { method_of_t1 } } struct S; impl S { // Visibility is allowed here. create_method! { pub method_of_s } } impl T1 for S {} fn main() { let s = S; s.method_of_t1(); s.method_of_s(); }