以下内容为本人学习过程中的记录笔记,其中可能存在不准确或错误,欢迎勘误及指正
Trait的作用
在Rust中Trait(特质)是一种任何类型都可以选择支持或不支持的一种定义行为的机制,Trait可以被认为是某类型能够做什么的一种能力,其类似于其他语言中的接口,但存在一定区别。Trait的作用主要包含(1)定义共享行为、(2)实现多态、(3)扩展类型的功能、(4)提高代码可读性和可维护性、(5)约束泛型类型(见泛型部分)
Trait的定义与实现
Trait的定义
使用trait
关键字定义一个Trait。Trait的定义是将方法的签名进行封装,以定义实现某种目的所必须的一组行为
1 2 3 4 5 6
| trait Shape { fn area(&self) -> f64; fn perimeter(&self) -> f64; }
|
在Trait的定义中只有方法签名而没有具体的实现,其中封装的方法的具体实现由实现该Trait的类型提供
Trait的实现
Trait中定义方法的实现和结构体或枚举方法的实现很相似,都是使用impl
关键字。具体写法为impl ... for ...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| trait Shape { fn area(&self) -> f64; fn perimeter(&self) -> f64; }
struct Rectangle { width: f64, height: f64, }
impl Shape for Rectangle { fn area(&self) -> f64 { self.width * self.height } fn perimeter(&self) -> f64 { 2.0 * (self.width + self.height) } }
struct Circle { radius: f64, }
impl Shape for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius } fn perimeter(&self) -> f64 { 2.0 * std::f64::consts::PI * self.radius } }
fn main() { let rec = Rectangle { width: 12.0, height: 20.0, };
println!("area: {}, perimeter: {}", rec.area(), rec.perimeter()); }
|
在上面的例子中,“圆”和“矩形”是不同的类型,但我们可以在函数中使用同样的函数签名来调用它们绑定的方法
Trait的实现约束
在类型上实现Trait存在两个限制条件
- 这个类型`或`这个Trait(至少有一个)是在本地crate定义的 ,比如我们可以为Rectangle类型实现Debug Trait,也可以为Vector类型实现Shape Trait
- 为确保两个不同库的代码不互相影响,在Rust中无法为外部类型实现外部Trait ,比如我们无法为标准库中的String类型实现Display Trait
Trait默认实现与重载
Trait的默认实现
在前面,我们说Trait所封装的方法的实现由实现这个Trait的类型提供,但在定义Trait时其实也可以提供默认实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| trait Shape { fn perimeter(&self) -> f64; fn area(&self) -> f64 { println!("default method, return perimeter"); self.perimeter() } }
struct Rectangle { width: f64, height: f64, }
impl Shape for Rectangle { fn perimeter(&self) -> f64 { 2.0 * (self.width + self.height) } }
fn main() { let rec = Rectangle { width: 12.0, height: 20.0, }; println!("rec area: {}", rec.area()); }
|
上面的例子中,Trait在定义时就提供了默认实现。但需要注意的是,默认实现无法获得类型中的字段(field) ,因为Trait定义的是具体类型的共享行为,它无法知道用户定义的类型会提供什么数据,比如我们在矩形中我们提供了长和宽,而圆只提供了半径
Trait的重载
虽然Trait可以提供默认实现,但我们也可以针对特定类型进行Trait的重载,重载不会影响其他类型调用默认实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| trait Shape { fn perimeter(&self) -> f64; fn area(&self) -> f64 { println!("default method, return perimeter"); self.perimeter() } }
struct Circle { radius: f64, }
impl Shape for Circle { fn perimeter(&self) -> f64 { 2.0 * std::f64::consts::PI * self.radius } fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius } }
fn main() { let cir = Circle { radius: 5.0, }; println!("cir area: {}", cir.area()); }
|
Trait作为传入类型和返回类型
作为传入类型
在Rust中,我们可以将Trait作为函数传入的参数类型,就可以实现将多种具体的类型传入同一函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| trait Shape { fn perimeter(&self) -> f64; fn area(&self) -> f64; }
struct Rectangle { width: f64, height: f64, }
impl Shape for Rectangle { fn perimeter(&self) -> f64 { 2.0 * (self.width + self.height) } fn area(&self) -> f64 { self.width * self.height } }
fn main() { let rec = Rectangle { width: 12.0, height: 20.0, };
trait_param(rec); }
fn trait_param(item: impl Shape) { println!("{}", item.area()); }
|
在上面的例子中,trait_param()
这个函数接收一个实现了Shape trait的类型,在调用时可以将任何实现Shape trait的类型传入。当我们想约束传入的类型需要实现多个Trait时,Trait之间可以使用+
号连接
作为返回类型
和传入参数时类似,Trait也可以作为返回类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| use std::fmt::Debug;
trait Shape { fn perimeter(&self) -> f64; fn area(&self) -> f64; }
#[derive(Debug)] struct Rectangle { width: f64, height: f64, }
impl Shape for Rectangle { fn perimeter(&self) -> f64 { 2.0 * (self.width + self.height) } fn area(&self) -> f64 { self.width * self.height } }
fn main() { let rec = return_trait(); println!("{:?}", rec); }
fn return_trait() -> impl Shape + Debug { Rectangle { width: 11.0, height: 9.0, } }
|
上面的例子中,return_trait()
函数会返回一个实现Shape trait和Debug trait的类型。但是需要注意的是,当我们使用特定trait作为返回类型时,用户必须保证这个函数返回的具体类型是确定的 ,否则就会像下面例子中的代码一样出现错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| use std::fmt::Debug;
trait Shape { fn perimeter(&self) -> f64; fn area(&self) -> f64; }
#[derive(Debug)] struct Rectangle { width: f64, height: f64, }
impl Shape for Rectangle { fn perimeter(&self) -> f64 { 2.0 * (self.width + self.height) } fn area(&self) -> f64 { self.width * self.height } }
#[derive(Debug)] struct Circle { radius: f64, }
impl Shape for Circle { fn perimeter(&self) -> f64 { 2.0 * std::f64::consts::PI * self.radius } fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius } }
fn main() { let rec = return_trait(false); println!("{:?}", rec); }
fn return_trait(flag: bool) -> impl Shape + Debug { if flag { Rectangle { width: 11.0, height: 9.0, } } else { Circle { radius: 7.0 } } }
|
以上的例子是无法通过编译的,但如果我们确实需要实现与例子中return_trait()
函数类似的功能也是有办法的,具体见Trait对象部分