【RUST_BASIC】Rust 高级 trait
1 关联类型
关联类型(associated types)是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
trait 的实现者会针对特定的实现在这个类型的位置指定相应的具体类型:
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
// --snip--
只能选择一次 Item
的类型,因为只能有一个 impl Iterator for Counter
。当调用 Counter
的 next
时不必每次指定我们需要 u32
值的迭代器。
2 trait 中的默认泛型
当使用泛型类型参数时,可以为泛型指定一个默认的具体类型。如果默认类型就足够的话,这消除了为具体类型实现 trait 的需要。为泛型类型指定默认类型的语法是在声明泛型类型时使用 ·<PlaceholderType=ConcreteType>·:
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
<RHS=Self>
语法叫做默认类型参数(default type parameters),RHS
是一个泛型类型参数(“right hand side” 的缩写),它用于定义 add
方法中的 rhs
参数,如果实现 Add
trait 时不指定 RHS
的具体类型,RHS
的类型将是默认的 Self
类型。
不带参数如下:
use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 });
}
带参数如下:
use std::ops::Add;
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
默认参数类型主要用于如下两个方面:
- 扩展类型而不破坏现有代码。
- 在大部分用户都不需要的特定情况进行自定义。
3 完全限定语法
一个 trait 与另一个 trait 拥有相同名称的方法:
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
可如下进行方法的区分:
fn main() {
let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
}
但是关联函数是 trait 的一部分,但没有 self 参数,当同一作用域的两个类型实现了同一 trait,Rust 就不能计算出我们期望的是哪一个类型:
trait Animal {
fn baby_name() -> String;
}
struct Dog;
impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}
impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}
fn main() {
println!("A baby dog is called a {}", Dog::baby_name());
}
当调用 Animal::baby_name()
将报错,Animal::baby_name
是关联函数而不是方法,因此它没有 self
参数,无法计算出所需的是哪一个 Animal::baby_name
实现,此时需要使用完全限定语法(fully qualified syntax):
fn main() {
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
通常,完全限定语法定义为:
<Type as Trait>::function(receiver_if_method, next_arg, ...);
只有当存在多个同名实现而 Rust 需要帮助以便知道调用哪个实现时,才需要使用这个较为冗长的语法。
4 newtype 模式用以在外部类型上实现外部 trait
如果想要在 Vec<T>
上实现 Display
,而孤儿规则阻止,因为 Display
trait 和 Vec<T>
都定义于我们的 crate 之外。
一个绕开这个限制的方法是使用 newtype 模式:可以创建一个包含 Vec<T>
实例的 Wrapper
结构体,在 Wrapper
上实现 Display
并使用 Vec<T>
的值:
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}
此方法的缺点是,因为 Wrapper
是一个新类型,它没有定义于其值之上的方法,必须直接在 Wrapper
上实现 Vec<T>
的所有方法,这样就可以代理到 self.0
上,如果希望新类型拥有其内部类型的每一个方法,可为封装类型实现 Deref
trait,并返回其内部类型是一种解决方案;如果不希望封装类型拥有所有内部类型的方法,则必须只自行实现所需的方法。