定义和实现 Trait
定义 trait 比较简单, 其语法如下:
#![allow(unused)] fn main() { pub trait TraitName { fn method1(&self, ...) -> Retrun1; fn function2(...) -> Return2; ... } }
先看一下标准库中 fmt::Debug
trait:
#![allow(unused)] fn main() { pub trait Debug { // Required method fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result; } }
该 trait 只声明了一个方法 fmt()
, 要为自己定义的类型实现这个方法, 也很简单:
#![allow(unused)] fn main() { use std::fmt; pub struct Point { x: f32, y: f32, } impl fmt::Debug for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Point") .field("x", &self.x) .field("y", &self.y) .finish() } } }
另一个常用的 trait 是 Default
trait, 用于实现类型的默认值, 其定义如下:
#![allow(unused)] fn main() { pub trait Default: Sized { // Required method fn default() -> Self; } }
接下来给上面的 Point
结构体实现这个 trait:
#![allow(unused)] fn main() { impl Default for Point { fn default() -> Self { Self { x: 0.0, y: 0.0, } } } }
实现方法
在 trait 中, 可以只声明方法 (method declaration), 也可以同时定义方法 (method default definition), 即编写该方法的默认实现, 但可以被外部类型所覆盖.
看一下标准库中的例子:
#![allow(unused)] fn main() { }
继承 trait
#![allow(dead_code)] trait Person { fn name(&self) -> String; } trait Student: Person { fn university(&self) -> String; } trait Programmer { fn favorite_language(&self) -> String; } trait ComputerScienceStudent: Programmer + Student { fn git_username(&self) -> String; } fn comp_sci_student_greeting(student: &dyn ComputerScienceStudent) -> String { format!( "My name is {} and I attend {}. My git username is {}", student.name(), student.university(), student.git_username() ) } fn main() {}
空的 trait
标准库中定义了好几个空的 trait, 这些 trait 只有名称, 没有约束任何的方法或者别的类型, 比如:
- Sized
- Copy
- Send
- Sync
声明它们的代码很简单:
#![allow(unused)] fn main() { pub trait Copy: Clone { } pub trait Sized { } pub unsafe auto trait Send { } pub unsafe auto trait Sync { } }
通常这些类型都被编译器使用:
- Copy trait 可以让类型通过拷贝比特位来复制其值
- Send/Sync, 用于实现跨线程访问共享的状态
- Sized, 要求类型在编译期有确定的内存大小占用, 否则就是 dynamic sized type
为外部类型实现外部的 trait
Rust 语言中的规则是, 要么类型是自己定义的, 要么 trait 是自己声明的, 这样的话才能给类型实现指定的 trait.
但有时候要给标准库或者第三方库里的类型实现外部的 trait, 怎么处理? 此时可以先定义一个新的结构体,
然后给这个结构体实现已存在的 trait, 这个方法被称为 New Type Idiom
.
标准库并没有为 f32
和 f64
实现 Eq
, Ord
, Hash
等 traits, 但有时又确实需要实现这些
trait, 看下面的一个简陋的例子:
#![allow(unused)] fn main() { use std::cmp::Ordering; use std::hash::{Hash, Hasher}; #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] pub struct F32(f32); impl Eq for F32 {} #[allow(clippy::derive_ord_xor_partial_ord)] impl Ord for F32 { fn cmp(&self, other: &Self) -> Ordering { self.0.to_be_bytes().cmp(&other.0.to_be_bytes()) } } impl Hash for F32 { fn hash<H: Hasher>(&self, state: &mut H) { self.0.to_bits().hash(state); } } #[cfg(test)] mod tests { use super::F32; #[test] fn test_f32_equal() { let f1 = F32(std::f32::consts::PI); let f2 = F32(std::f32::consts::PI); assert_eq!(f1, f2); } } }