Traits

语法
Trait :
   unsafe? trait 标识符  泛型参数组? ( : 类型参数约束组? )? Where从句? {
     内部属性*
     关联条目*
   }

trait 描述了类型可以实现的抽象接口。这个接口由 关联条目 组成,包括以下三种:

所有 trait 都定义了一个隐式类型参数 Self ,该参数指代 "正在实现此接口的类型" 。 Trait 还可以包含其他类型参数。这些类型参数以及 Self泛型 可以接受其他 trait 约束。

Trait 通过不同的 实现 与特定的类型关联。

Trait 函数可以省略函数体,用分号代替。这表示实现必须定义该函数。 如果 trait 函数定义了函数体,则作为未覆盖此函数实现时的默认值。 同样地,关联常量可以省略等号和表达式,以表示实现必须定义常量值。 关联类型绝不能定义类型,类型只能在实现中指定。

#![allow(unused)]
fn main() {
// 有定义和无定义的 trait 关联条目的示例。
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) {}
}
}

Trait 函数不允许 asyncconst

Trait 约束

泛型条目可以使用 trait 作为其类型参数的 约束

泛型 Trait

可以在 Trait 名称后指定类型参数 ,使 Trait 泛化。 参数语法与 泛型函数 的相同。

#![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);
}
}

对象安全

对象安全的 trait 可以作为 trait 对象 的基础 trait 。 如果具有以下特性 (在RFC 255中定义) ,则 trait 是 对象安全 的:

  • 所有的 父级traits 也必须是对象安全的。
  • Sized 不能是 父级traits 。换句话说,必须不要求 Self: Sized
  • 不能有任何关联常量。
  • 不能有任何泛型关联类型。
  • 所有关联函数必须是可从 trait 对象中可派发的,或者是明确不可派发的。
    • 可派发函数要求:
      • 没有任何类型参数 (允许生命周期参数),
      • 是除接收者类型,不使用 Self方法
      • 有具有以下类型的接收者:
      • 没有 where Self: Sized 绑定 (接收者类型 Self (即 self) 暗指 this) 。
    • 显式不可派发函数要求:
      • where Self: Sized 绑定 (接收者类型 Self (即 self) 暗指 this) 。
#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::sync::Arc;
use std::pin::Pin;
// 对象安全方法的示例。
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() {
// 这个 trait 是对象安全的,但这些方法不能在 trait 对象上派发。
trait NonDispatchable {
    // 非方法,不能被派发。
    fn foo() where Self: Sized {}
    // Self 类型只有在运行时才知道。
    fn returns(&self) -> Self where Self: Sized;
    // `other` 可能是接收者的不同的具体类型。
    fn param(&self, other: Self) where Self: Sized {}
    // 泛型与虚表不兼容。
    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: 不能调用 Self 返回
obj.param(S);  // ERROR: 不能调用 Self 参数
obj.typed(1);  // ERROR: 不能调用泛型类型
}
#![allow(unused)]
fn main() {
use std::rc::Rc;
// 非对象安全 trait 的示例。
trait NotObjectSafe {
    const CONST: i32 = 1;  // ERROR: 不能有关联 const

    fn foo() {}  // ERROR: 关联函数没有 Sized
    fn returns(&self) -> Self; // ERROR: Self 在返回类型
    fn typed<T>(&self, x: T) {} // ERROR: 有泛型类型参数
    fn nested(self: Rc<Box<Self>>) {} // ERROR: 尚不支持嵌套的接收者
}

struct S;
impl NotObjectSafe for S {
    fn returns(&self) -> Self { S }
}
let obj: Box<dyn NotObjectSafe> = Box::new(S); // ERROR
}
#![allow(unused)]
fn main() {
// Self: Sized traits 不是 object-safe.
trait TraitWithSize where Self: Sized {}

struct S;
impl TraitWithSize for S {}
let obj: Box<dyn TraitWithSize> = Box::new(S); // ERROR
}
#![allow(unused)]
fn main() {
// 如果 `Self` 是类型参数,则不是对象安全的。
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: 不能使用 `Self` 类型的参数
}

父级Trait

父级trait 是指在一个类型实现某个特定 trait 之前必须实现的一些 trait。 此外,在 泛型trait 对象 受到 trait 约束时,它可以访问其父级trait的关联条目。

父级 Trait 可以通过在 trait 的 Self 类型上使用 trait 约束声明,而父级 Trait 的约束将在其内部声明的 trait 的父级 Trait 中传递。trait 不能是自己的父级 Trait ,否则是错误。

具有父级 Trait 的 trait 被称为其父级 Trait 的 子trait

以下是将 Shape 声明为 Circle 的父级trait的示例。

#![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
        // 对于代数,
        // r = sqrt(A / pi)
        (self.area() /std::f64::consts::PI).sqrt()
    }
}
}

这个例子展示了在一个泛型参数上调用父级trait 方法。

#![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) {
    // 这里从 `Circle` 的 supertrait `Shape` 中调用 area 方法。
    println!("Area: {}", c.area());
    println!("Radius: {}", c.radius());
}
}

类似地,以下是在 trait 对象上调用父级trait 方法的示例。

#![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 traits

Trait 中以 unsafe 关键字开头的条目表示实现该 trait 可能是非安全的。 使用正确实现的非安全 trait 是安全的。相应的 trait 实现 也必须以 unsafe 关键字开头。

SyncSend 是 unsafe trait 的示例。

参数模式

未设置函数或方法的实现体只允许使用 标识符 或者 _ 通配符模式。 目前允许使用 mut 标识符,但是这种方式已经被弃用,并且将来会变成一个严格的错误。

在 2015 版中,trait 函数或方法参数的模式是可选的。

#![allow(unused)]
fn main() {
// 2015 版
trait T {
    fn f(i32);  // 不需要参数标识符。
}
}

函数或方法的参数模式种类被限制为以下之一:

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

#![allow(unused)]
fn main() {
trait T {
    fn f1((a, b): (i32, i32)) {}
    fn f2(_: (i32, i32));  // 没有主体不能使用元组模式。
}
}

条目可见性

Trait 中的条目在语法上允许添加 可见性 注解,但是当验证该 trait 时,这些注解会被拒绝。 这使得在使用这些条目的不同上下文中,可以使用统一的语法进行解析。 例如,可以使用一个空的 vis 宏片段规格来表示 trait 条目,在其他允许使用可见性的情况下使用该宏规则。

macro_rules! create_method {
    ($vis:vis $name:ident) => {
        $vis fn $name(&self) {}
    };
}

trait T1 {
    // 允许使用空的 `vis` 。
    create_method! { method_of_t1 }
}

struct S;

impl S {
    // 这里允许可见性。
    create_method! { pub method_of_s }
}

impl T1 for S {}

fn main() {
    let s = S;
    s.method_of_t1();
    s.method_of_s();
}