实现特性

当一个类型是在另一个crate中定义的(例如,来自Rust标准库的u32),你不能直接为它定义新的方法。如果你尝试这样做:

#![allow(unused)]
fn main() {
impl u32 {
    fn is_even(&self) -> bool {
        self % 2 == 0
    }
}
}

编译器会报错:

error[E0390]: cannot define inherent `impl` for primitive types
  |
1 | impl u32 {
  | ^^^^^^^^
  |
  = help: consider using an extension trait instead

扩展特性

扩展特性是一种主要目的是向外部类型(如u32)附加新方法的特性。这正是你在上一个练习中采用的模式,通过定义IsEven特性然后为i32u32实现它。只要IsEven在作用域内,你就可以自由地在这些类型上调用is_even方法。

// 引入特性
use my_library::IsEven;

fn main() {
    // 在实现了它的类型上调用其方法
    if 4.is_even() {
        // [...]
    }
}

单一实现

在你能编写的特性实现中存在一些限制。最简单且最直接的一个是:你不能在一个crate中,为同一个类型两次实现同一个特性。

例如:

#![allow(unused)]
fn main() {
trait IsEven {
    fn is_even(&self) -> bool;
}

impl IsEven for u32 {
    fn is_even(&self) -> bool {
        true
    }
}

impl IsEven for u32 {
    fn is_even(&self) -> bool {
        false
    }
}
}

编译器会拒绝它:

error[E0119]: conflicting implementations of trait `IsEven` for type `u32`
   |
5  | impl IsEven for u32 {
   | ------------------- first implementation here
...
11 | impl IsEven for u32 {
   | ^^^^^^^^^^^^^^^^^^^ conflicting implementation for `u32`

当在u32值上调用IsEven::is_even时,不能存在任何关于应该使用哪个特性实现的歧义,因此只能有一个。

孤儿规则

当涉及多个crate时,情况变得更加微妙。特别地,以下至少有一项必须为真:

  • 特性在当前crate中定义
  • 实现者类型在当前crate中定义

这被称为Rust的孤儿规则。其目的是使方法解析过程无歧义。

想象以下情形:

  • Crate A 定义了IsEven特性
  • Crate Bu32实现了IsEven
  • Crate C 提供了IsEven特性针对u32的不同实现
  • Crate D 同时依赖于BC,并调用1.is_even()

应该使用哪个实现?是B中定义的吗?还是C中定义的?没有明确的答案,因此定义了孤儿规则以防止这种情况发生。得益于孤儿规则,无论是crate B还是crate C都不会编译成功。

参考资料

  • 本节练习位于 exercises/04_traits/02_orphan_rule

进一步阅读

  • 如上所述的孤儿规则有一些例外和注意事项。如果你想了解其细节,请查阅参考文档