20_rust的Trait
Trait
Trait告诉Rust编译器某些类型具有哪些并可与其它类型共享的功能。
Trait:抽象的定义共享行为。
Trait bounds(约束):泛型类型参数指定为实现了特定行为的类型。
Trait与其它语言的接口(interface)类似,但有些区别。
定义一个Trait
Trait的定义:把方法签名放在一起,来定义实现某种目的所需的一组行为。
- 关键字:trait
- 只有方法签名,没有具体实现
- trait可有多个方法:每个方法签名占一行,以分号(;)结尾
- 实现该trait的类型必须提供具体的方法实现
如:
pub trait Sum { // 定义了一个名为Sum的trait
fn summarize($self) -> String;
}
在类型上实现trait
与类型实现的方法类似,不同之处需要写上trait名称:
- 代码块:impl TraitName for StrutName {}
- 在impl代码块里,需要对Trait里的方法签名进行具体实现
pub trait SpecialInfo {
fn get_info(&self) -> String;
}
pub struct Aa {
pub x: String,
pub y: String,
pub z: String,
}
impl SpecialInfo for Aa {
fn get_info(&self) -> String {
format!("{}, {}", self.x, self.y)
}
}
pub struct Bb {
pub x: String,
pub y: i32,
pub z: i32,
}
impl SpecialInfo for Bb {
fn get_info(&self) -> String {
format!("{}={}", self.x, self.z)
}
}
fn main() {
let a = Aa {x: String::from("x"), y: String::from("y"), z: String::from("z")};
let b = Bb {x: String::from("x"), y: 3, z: 6};
println!("{}", a.get_info());
println!("{}", b.get_info());
}
/*输出:
x, y
x=6
*/
实现trait的约束
可在某个类型上实现某个trait的前提条件是:这个类型或这个trait是在本地crate里定义的。
无法为外部类型实现外部的trait:
- 此限制是程序属性的一部分(一致性)。
- 孤儿规则:命名缘由父类型不存在。
- 如果无此规则,两个crate可为同一类型实现同一trait,rust就不知应使用哪个实现了。
默认实现
可不用为每个类型都实现特定的trait,比如一些共性的运算操作,用一个默认实现即可,这样就只需对特殊类型做特殊适配即可。
同时也可选择保留或重写某个实现。
pub trait SpecialInfo {
fn get_info(&self) -> String { // 默认实现
String::from("default impl")
}
}
pub struct Aa {
pub x: String,
pub y: String,
pub z: String,
}
impl SpecialInfo for Aa {
fn get_info(&self) -> String { // 重写实现
format!("{}, {}", self.x, self.y)
}
}
pub struct Bb {
pub x: String,
pub y: i32,
pub z: i32,
}
impl SpecialInfo for Bb { // 使用默认实现
}
fn main() {
let a = Aa {x: String::from("x"), y: String::from("y"), z: String::from("z")};
let b = Bb {x: String::from("x"), y: 3, z: 6};
println!("{}", a.get_info());
println!("{}", b.get_info()); // 会调用默认实现
}
/*输出:
x, y
default impl
*/
默认实现的方法可调用trait中其它的方法,即使这些方法没有默认实现。
pub trait SpecialInfo {
fn collect_info(&self) -> String;
fn get_info(&self) -> String { // 可调用未实现的trait
format!("default impl {}", self.collect_info())
}
}
pub struct Aa {
pub x: String,
pub y: String,
pub z: String,
}
impl SpecialInfo for Aa {
fn collect_info(&self) -> String { // 必须实现这个没有实现的trait,虽然未使用
format!("{}, {}", self.x, self.z)
}
fn get_info(&self) -> String { // 重写实现
format!("{}, {}", self.x, self.y)
}
}
pub struct Bb {
pub x: String,
pub y: i32,
pub z: i32,
}
impl SpecialInfo for Bb { // 使用默认实现
fn collect_info(&self) -> String {
format!("{}= {}", self.x, self.z)
}
}
fn main() {
let a = Aa {x: String::from("x"), y: String::from("y"), z: String::from("z")};
let b = Bb {x: String::from("x"), y: 3, z: 6};
println!("{}", a.get_info());
println!("{}", b.get_info()); // 会调用默认实现
}
/*输出:
x, y
default impl x= 6
*/
注意:无法从方法的重写实现里调用默认的实现。
trait作为参数
进一步修改上面的例子,增加一个打印函数,trait作为参数,最终实现类似于多态的效果:
pub trait SpecialInfo {
fn collect_info(&self) -> String;
fn get_info(&self) -> String {
format!("default impl {}", self.collect_info())
}
}
pub struct Aa {
pub x: String,
pub y: String,
pub z: String,
}
impl SpecialInfo for Aa {
fn collect_info(&self) -> String {
format!("{}, {}", self.x, self.z)
}
fn get_info(&self) -> String {
format!("{}, {}", self.x, self.y)
}
}
pub struct Bb {
pub x: String,
pub y: i32,
pub z: i32,
}
impl SpecialInfo for Bb {
fn collect_info(&self) -> String {
format!("{}= {}", self.x, self.z)
}
}
pub fn display_info(item: impl SpecialInfo) { // 参数表示只要实现了SpecialInfo就可调用相应的方法
println!("info: {}", item.get_info());
}
fn main() {
let a = Aa {x: String::from("x"), y: String::from("y"), z: String::from("z")};
let b = Bb {x: String::from("x"), y: 3, z: 6};
display_info(a);// 会调用自己实现的方法(类似其他语言的多态)
display_info(b);
}
/*输出:
info: x, y
info: default impl x= 6
*/
1)这种方式是使用impl Trait语法
:适用于简单情况。
2)另一种使用Trait bound
语法:可用于复杂情况。实际impl Trait语法是Trait bound的语法糖。
对比:
pub fn display_info1(item: impl SpecialInfo, item2: impl SpecialInfo) { // impl Trait语法
println!("info: {}", item.get_info());
}
pub fn display_info2<T: SpecialInfo>(item: T, item2: T) { // Trait bound语法实现
println!("info: {}", item.get_info());
}
可使用+号
指定多个Trait bound。
use std::fmt::Display
pub fn display_info1(item: impl SpecialInfo + Display) { //要求同时实现SpecialInfo和Display这两个trait
println!("info: {}", item.get_info());
}
pub fn display_info2<T: SpecialInfo + Display>(item: T, item2: T) { // Trait bound语法方式
println!("info: {}", item.get_info());
}
不过上面这种写法,会导致函数签名很长,不直观,所以rust又提供了另外一种写法:
在Trait bound语法中使用where子句:在方法签名之后指定where子句。
pub fn notify<T: SpecialInfo + Display, U: Clone + Debug>(i: T, j: U) -> String {
format!("info: {}", i.get_info())
}
// 可用下边这种实现
pub fn notify<T, U>(i: T, j: U) -> String
where
T: SpecialInfo + Display,
U: Clone + Debug,
{
format!("info: {}", i.get_info())
}
使用trait作为返回类型
使用impl Trait语法
注意:impl Trait只能返回确定的同一种类型,返回可能不同类型的代码会报错。
比如:(基于上文的代码)
pub fn get_obj() -> impl SpecialInfo {// 正确写法
Aa {x: String::from("x"), y: String::from("y"), z: String::from("z")}
}
pub fn get_obj2(flg: bool) -> impl SpecialInfo {
if (flg) { // 虽然这两个struct都实现了SpecialInfo,但返回类型不确定,所以编译报错
Aa {x: String::from("x"), y: String::from("y"), z: String::from("z")}
} else {
Bb {x: String::from("x"), y: 3, z: 6}
}
}
使用Trait bound有条件的实现方法
在使用泛型类型参数的impl块上使用Trait bound,我们可有条件的为实现了特定Trait的类型来实现方法。
这句话的含义看下边例子:(简单理解就是有些T可根据实现的trait有特殊的方法)
use std::fmt::Display
struct Pai<T> {
x: T,
y: T,
}
impl<T> Par<T> { //对泛型struct par,无论类型T是什么类型的,都实现了一个new函数
fn new(x: T, y: T) -> self { // 所有的par类型,无论T是什么,都有一个new函数
Self {x, y}
}
}
// 这里对T进行了约束,需要T同时实现了Display和PartialOrd两个trait,才会拥有里边的函数
impl<T: Display + PartialOrd> Par<T> {
fn cmp_print(&self) { // 只有T实现了Display和PartialOrd两个trait,才有cmp_print方法
if self.x >= self.y {
println!("max num x={}", self.x);
} else {
println!("max num y={}", self.y);
}
}
}
也可为实现了其它Trait的任意类型有条件的实现某个Trait。
为满足Trait Bound的所有类型上实现Trait叫做覆盖实现(blanket implementations)。
看库代码string.rs里的内容作为例子:
impl<T: fmt::Display> ToString for T {...}
// 含义是要求T实现Display trait,只要实现了Display的T,都实现了ToString trait,这就是覆盖实现,
// 对所有实现了Display trait的类型都可以调用ToString trait里的方法(to_string方法),如
let s = 3.to_string();
// 把整数3转成String类型,因为整数实现了Display trait,而在标准库中,针对所有实现了Display trait的类型都实现了
// ToString trait,在ToString trait里有to_string方法。
一个例子:使用Trait bound修复泛型函数的问题
之前的一个例子,想实现一个函数,能够返回任意类型的集合的最大值。
fn get_max<T>(list: &[T]) -> T {
let mut max_ = list[0];
for &i in list.iter() {
if i > max_ {
max_ = i;
}
}
}
fn main() {
let nums = vec![23, 12, 34, 2, 56];
let ret = get_max(&nums);
println!("{}", ret);
let chs = vec!['a', 'd', 'm', 'e'];
let ret = get_max(&chs);
println!("{}", ret);
}
/* 编译报错
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src\main.rs:4:14
4 | if i > max_ {
| - ^ ---- T
| |
| T
help: consider restricting type parameter `T`
|
1 | fn get_max<T: std::cmp::PartialOrd>(list: &[T]) -> T {
*/
上面代码直接写,会报大于号无法比较T类型的值,大于号实际是std::cmp::PartialOrd这个trait里的一个默认方法,只有T类型实现了这个Trait里的大于号才能进行比较。PartialOrd是预导入模块里的,无需手动导入,所以修改办法是只要加入到代码中即可:
fn get_max<T: PartialOrd>(list: &[T]) -> T { // 增加PartialOrd trait实现
let mut max_ = list[0];
for &i in list.iter() {
if i > max_ {
max_ = i;
}
}
max_
}
fn main() {
let nums = vec![23, 12, 34, 2, 56];
let ret = get_max(&nums);
println!("{}", ret);
let chs = vec!['a', 'd', 'm', 'e'];
let ret = get_max(&chs);
println!("{}", ret);
}
/* 编译报错
error[E0508]: cannot move out of type `[T]`, a non-copy slice
--> src\main.rs:2:20
|
2 | let mut max_ = list[0];
| ^^^^^^^
| |
| cannot move out of here
| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
|
help: consider borrowing here
*/
不过又报新的错误了,意思是无法移除元素,因为没有实现Copy trait,建议考虑使用借用的方式。main函数内的调用方,要么是int数据,要么是char类型,都是固定大小,存储在栈上的,都默认实现了Copy trait。但在泛型函数get_max里的T却没加上Copy trait的约束,所以加上即可:
fn get_max<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut max_ = list[0];
for &i in list.iter() {
if i > max_ {
max_ = i;
}
}
max_
}
fn main() {
let nums = vec![23, 12, 34, 2, 56];
let ret = get_max(&nums);
println!("{}", ret);
let chs = vec!['a', 'd', 'm', 'e'];
let ret = get_max(&chs);
println!("{}", ret);
}
/* cargo run运行结果
56
m
*/
终于运行成功;但还是无法String类型,因为String在堆上,没有默认实现Copy trait。但String实现了Clone trait,所以可改成Clone:
fn get_max<T: PartialOrd + Clone>(list: &[T]) -> T {
let mut max_ = list[0];
for &i in list.iter() {
if i > max_ {
max_ = i;
}
}
max_
}
fn main() {
let nums = vec![23, 12, 34, 2, 56];
let ret = get_max(&nums);
println!("{}", ret);
let chs = vec!['a', 'd', 'm', 'e'];
let ret = get_max(&chs);
println!("{}", ret);
let strs = vec![String::from("y"), String::from("x"), String::from("r")];
let ret = get_max(&strs);
println!("{}", ret);
}
但编译还是报原来的错误,一个是let mut max_ = list[0]报原来的错误,需要使用clone()方法,list.iter()要求实现Copy trait,但T没有实现Copy trait,所以可不让这行发生数据的移动,只引用下即可:
fn get_max<T: PartialOrd + Clone>(list: &[T]) -> T {
let mut max_ = list[0].clone();
for i in list.iter() { // i原来是T类型,去除&号,变成&T类型
if i > &max_ { // i去除&后又会报错,因为i是&T,但max_是T类型,所以需要给max_加上&号
max_ = i.clone(); // 此时也要进行一次clone
}
}
max_
}
fn main() {
let nums = vec![23, 12, 34, 2, 56];
let ret = get_max(&nums);
println!("{}", ret);
let chs = vec!['a', 'd', 'm', 'e'];
let ret = get_max(&chs);
println!("{}", ret);
let strs = vec![String::from("y"), String::from("x"), String::from("r")];
let ret = get_max(&strs);
println!("{}", ret);
}
此时运行没问题,另一种是返回值是引用类型,就不需要clone了,代码更简洁:
fn get_max<T: PartialOrd + Clone>(list: &[T]) -> &T {
let mut max_ = &list[0];// 这里取引用
for i in list.iter() {
if i > &max_ {
max_ = i;
}
}
max_
}
fn main() {
let nums = vec![23, 12, 34, 2, 56];
let ret = get_max(&nums);
println!("{}", ret);
let chs = vec!['a', 'd', 'm', 'e'];
let ret = get_max(&chs);
println!("{}", ret);
let strs = vec![String::from("y"), String::from("x"), String::from("r")];
let ret = get_max(&strs);
println!("{}", ret);
}