返回介绍

8.3 特征

发布于 2024-10-13 11:25:30 字数 4319 浏览 0 评论 0 收藏 0

特征(trait)定义共享行为(shared behavior),类似接口和抽象类的混合体。由函数、方法和常量声明组成,允许有默认实现。

  • 隐式类型参数 Self ,实现此特征的类型。
  • 以其他特征约束 Self ,以便在默认实现中调用。
  • 声明或定义 &self&mut selfself 方法。
  • 声明或定义类型函数( Self::function )。
  • 必须实现仅提供声明的特征成员。
  • 实现语句块内,不能有非特征成员。
  • 成员默认实现均可被覆盖。
  • 无成员特征仅作为标记(marked),表明实现类型具有某种属性。
  • 所有成员都实现了某标记特征,则该类型也自动实现。
trait ToInt {
  const SEED: i32;       // 常量。
  fn new(_: i32) -> Self;  // 函数。
  fn to_int(&self) -> i32;   // 方法。
}

/* ------------------------------------------------------ */

#[derive(Debug)]
struct Data {
  x: i32
}

impl ToInt for Data {
  const SEED: i32 = 100;  // 必须!
  
  fn new(x: i32) -> Self  { Self{ x } }
  fn to_int(&self) -> i32 { Self::SEED + self.x }

  // 不能包含非 trait 成员。
  // 可实现为方法,然后在 trait 内以 self.method 调用。
  
  // fn add(&self, x: i32, y: i32) -> i32 { x + y }
  //  ^^^ not a member of trait `ToInt`
}

/* ------------------------------------------------------ */

fn main() {
  let d = Data::new(1);
  assert_eq!(d.to_int(), 101);
}

覆盖

说特征像抽象类(abstrace class),是因为它可以提供默认(default impl)实现。

use std::fmt::Debug;

trait ToInt where Self: Debug {  // 添加约束。
  fn new(_: i32) -> Self;

  // 默认常量。
  const SEED: i32 = 0;
  
  // 默认方法。
  fn to_int(&self) -> i32 {
    Self::SEED
  }

  fn print(&self) {
    println!("{:?}", self);
  }
}

/* ------------------------------------------------------ */

#[derive(Debug)]
struct Data {
  x: i32
}

impl ToInt for Data {
  fn new(x: i32) -> Self { Self{ x } }
}

/* ------------------------------------------------------ */

fn main() {
  let d = Data::new(10);
  assert_eq!(d.to_int(), 0);
}

可覆盖(override)默认实现,但无法在覆盖实现内访问默认实现。

impl ToInt for Data {
  fn new(x: i32) -> Self { Self{ x } }
  
  // 覆盖常量。
  const SEED: i32 = 100;
}

/* ------------------------------------------------------ */

fn main() {
  let d = Data::new(10);
  assert_eq!(d.to_int(), 100);
}
impl ToInt for Data {
  fn new(x: i32) -> Self { Self{ x } }

  // 可覆盖所有默认定义(常量、方法)。
  
  const SEED: i32 = 100;
  
  fn to_int(&self) -> i32 {
    Self::SEED + self.x
  }
}

/* ------------------------------------------------------ */

fn main() {
  let d = Data::new(10);
  assert_eq!(d.to_int(), 110);
}

扩展

与接口及抽象类不同,特征默认实现为目标类型提供非侵入式功能扩展(extension)。这是目标类型自己都不知道的外挂(可以成员方式调用),形如混入(mixins)。正因如此,要想使用特征扩展功能,须在当前作用域引入特征,让编译器能找到它。

mod ext {
  pub trait IntMixin {
    fn haha(&self) {}
  }

  impl IntMixin for i32 {}
}

/* ---------------------- */

fn main() {
  use ext::IntMixin;   // !!!!

  let d = 1;
  d.haha();
}

我们无法为外部类型添加方法,但特征可以。

trait IntMixin {
  fn haha(&self) {}
}

// impl Vec<i32> {}  // method
// ^^^^^^^^^^^^^^^^ impl for type defined outside of crate.

impl IntMixin for Vec<i32> {}  // trait

/* ---------------------- */

fn main() {
  let v = vec![1, 2];
  v.haha();
}

甚至于,基于另一个特征进行扩展。

use std::fmt::Debug;

trait Haha {
  fn haha(&self);
}

// 为实现 Debug 特征的类型添加扩展。
impl <T: Debug> Haha for T {
  fn haha(&self) {
    println!("{:?}, haha!", self);
  }
}

/* ---------------------- */

fn main() {
  1.haha();
  "abc".haha();
}

如果说接口表明 你能做什么,那么特征显然还有另一层意思 我能替你做什么。前者是约束,后者是扩展。

规则

实现特征时,类型或特征至少有一个是在当前 crate 内定义。 无法为外部类型实现外部特征 ,这称作 孤儿规则 (orphan rule)。

impl Clone for i32 {}
^^^^^^^^^^^^^^^---
|        |
|        `i32` is not defined in the current crate
impl doesn't use only types from inside the current crate

For more information about this error, try `rustc --explain E0117`.

该规则保证了特征实现的唯一性: trait + type

试想没有孤儿规则,那么多个第三方包都可以实现(外部特征 + 外部类型),编译器将无从选择。如此,就算在当前 crate 内,也不能重复实现。

trait Haha {}

impl Haha for i32 {}
----------------- first implementation here

impl Haha for i32 {}
^^^^^^^^^^^^^^^^^ conflicting implementation for `i32`

Tour of Rust's Standard Library Traits

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文