实现特性
当一个类型是在另一个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
特性然后为i32
和u32
实现它。只要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
B
为u32
实现了IsEven
- Crate
C
提供了IsEven
特性针对u32
的不同实现 - Crate
D
同时依赖于B
和C
,并调用1.is_even()
应该使用哪个实现?是B
中定义的吗?还是C
中定义的?没有明确的答案,因此定义了孤儿规则以防止这种情况发生。得益于孤儿规则,无论是crate B
还是crate C
都不会编译成功。
参考资料
- 本节练习位于
exercises/04_traits/02_orphan_rule
进一步阅读
- 如上所述的孤儿规则有一些例外和注意事项。如果你想了解其细节,请查阅参考文档。