文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
8.3 特征
特征(trait)定义共享行为(shared behavior),类似接口和抽象类的混合体。由函数、方法和常量声明组成,允许有默认实现。
- 隐式类型参数
Self
,实现此特征的类型。 - 以其他特征约束
Self
,以便在默认实现中调用。
- 声明或定义
&self
、&mut self
、self
方法。 - 声明或定义类型函数(
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`
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论