七、特性
Published on
特性是为未知类型Self定义的一组方法,类似于Java的接口。特性可以为任意类型实现,包括内置类型,如i64/&str等。
定义特性
// 定义一个特性,无需实现
pub trait Summary {
fn summarize(&self) -> String;
}
// 定义特性时可以提供默认实现
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
实现特性
特性必须为某个类型实现,可以认为是为这个类型添加了相应的方法。
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
使用impl Trait
impl Trait是一个语法糖,它相当于在泛型函数中使用泛型范围(trait bound)。可以将impl Trait作为函数的输入参数或者返回值,使代码更简洁易读,但是需要注意此时的函数是静态泛型函数,并不能动态接收或返回不同的类型。
作为输入参数
// 使用impl Trait作为输入参数
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// 使用+表示参数需要实现多个特性
pub fn notify(item: &(impl Summary + Display)) {}
作为返回值
// 使用impl Trait作为返回值
fn returns_summarizable() -> impl Summary {}
使用dyn返回实现特性的动态类型
Rust编译器必须知道函数返回值占用空间的大小,但是对于同样一个特性,不同的实现类型占用空间不同,因此不能直接将特性作为返回值。但是,可以通过返回一个Box指向一个实现特性的变量来解决这一问题。由于Box大小确定,因此这是可行的。由于Rust要求在堆上分配内存时必须显示说明,因此这种情况下需要使用dyn关键字。
struct Sheep {}
struct Cow {}
trait Animal {
fn noise(&self) -> &'static str;
}
impl Animal for Sheep {
fn noise(&self) -> &'static str {
"baaaaah!"
}
impl Animal for Cow {
fn noise(&self) -> &'static str {
"moooooo!"
}
}
fn random_animal(random_number: f64) -> Box<dyn Animal> {
if random_number < 0.5 {
Box::new(Sheep {})
} else {
Box::new(Cow {})
}
}
常用特性
操作符特性
Rust中的操作符是通过实现特性进行重载的。例如,加法是通过实现Add特性实现的。全部操作符特性列表可在这里找到。
Drop
Drop特性只有一个drop方法,会在变量离开作用域时自动调用。该特性用于完成释放资源的工作。
Iterator
Iterator特性只有一个next方法,用于获取下一个元素。该特性用于实现自定义迭代器。
Clone
Clone特性用于实现资源的复制。
派生(Derive)
通过使用#[derive]属性,Rust编译器可以为一些常用特性提供默认实现,这些特性也可以手动实现。
- 比较特性:Eq, PartialEq, Ord, PartialOrd
- Clone:生成一个变量的复制
- Copy:使用复制语义代替移动语义
- Hash:计算变量的hash值
- Default:为一个类型生成一个空值
- Debug:使用{:?}进行格式化
关联类型
关联类型是在定义特性时指定的类型占位符,在实现特性时才被指定具体类型。
// 特性声明
pub trait Iterator {
// 声明关联类型
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
// 特性实现
impl Iterator for Counter {
// 指定关联类型的实际类型
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
...
}
}
使用关联类型与使用泛型类似。但是如果使用泛型,则Counter的Iterator实现可以有多个,在使用时必须提供类型说明要使用哪个。如果使用关联类型,则Counter的Iterator实现只能有一个,使用时无需指定类型。
pub trait Iterator<T> {
fn next(&mut self) -> Option<T>;
}
默认泛型类型参数
当在特性中使用泛型参数时,可以提供默认值。
// Rhs的默认值为Self
trait Add<Rhs=Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
// 使用默认类型
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
...
}
}
// 使用指定类型
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
在两种情况下会用到默认类型参数:
- 在不修改现有代码的情况下扩展一个类型
- 提供大多数用户不需要的自定义功能
冲突特性歧义消解
一个类型可以实现多个特性,如果实现的两个特性的方法中,或者特性方法与类型方法中,有相同的方法名,则出现冲突。
- 实现方法时,不同的特性的方法实现在不同的代码块中,不会出现冲突
- 调用方法时,使用完全语法调用即可
// 两个特性具有相同方法名
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*");
}
}
let person = Human;
// 调用特性方法时需要使用完全语法指定特性
Pilot::fly(&person);
Wizard::fly(&person);
// 直接调用类型方法
person.fly();
在关联函数中,由于不存在self参数,因此需要一种更复杂的语法。
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")
}
}
// 可以直接使用类中的关联方法
println!("A baby dog is called a {}", Dog::baby_name());
// 错误,不能直接使用特性中的关联函数,因为不知道要使用哪个类型的实现
println!("A baby dog is called a {}", Animal::baby_name());
// 正确使用方式
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
父特性
Rust中并不存在继承,但是可以将一个特性定义为另一个的超集,实现子特性时要求必须实现父特性。
trait Person {
fn name(&self) -> String;
}
trait Student: Person {
fn university(&self) -> String;
}
使用newtype模式为外部类型实现外部特性
Rust不支持直接为外部类型实现外部接口,这样保证了代码的一致性。但是可以使用元组结构体声明一个外部类型的包装器,即可为该包装器实现外部特性。
// 声明外部类型Vec<String>的包装器
struct Wrapper(Vec<String>);
// 包装器为内部类型,可以实现外部特性
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}