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); }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战