Rust中的Trait是比较新的概念,英文有“特质”、“特征”的意思,我们也可以简单将其看做TS中的interface
Trait(特质、特征)
Trait告诉Rust编译器:
- 某种类型具有哪些并且可以与其他类型共享的功能
Trait:抽象的定义共享行为
Trait bounds(约束):泛型类型参数指定为实现了特定行为的类型
Trait与其他语言的接口(interface)类似,但也有区别。
定义一个Trait
Trait的定义:把方法签名放在一起,来定义实现某种目的所必须的一组行为。
- 关键字:trait
- 只有方法签名,没有具体实现
- trait可以有多个方法:每个方法签名占一行,以
;
结尾 - 实现该trait的类型必须提供具体的方法实现
pub trait CopyArticle {
fn copy(&self) -> String;
fn copy1(&self) -> String;
}
在类型上实现trait
与为类型实现方法类似。
不同之处:
- impl Xxxx for Tweet{...} (Xxxx是trait名)
- 在
impl
的块里,需要对Trait里的方法签名进行具体的实现
src/lib.rs
pub trait CopyArticle {
fn copy(&self) -> String;
}
pub struct Jps {
pub auther: String,
pub title: String,
pub content: String,
}
impl CopyArticle for Jps {
fn copy(&self) -> String {
format!(
"title:{},content:{},auther:{}",
self.title, self.content, self.auther
)
}
}
pub struct Book {
pub auther: String,
pub book_name: String,
}
impl CopyArticle for Book {
fn copy(&self) -> String {
format!("book_name is {},auther is {}", self.book_name, self.auther)
}
}
src/main.rs
use panic::Book;//panic是Cargo.toml中的[package]的name
use panic::CopyArticle;
use panic::Jps;
fn main() {
let book = Book {
book_name: String::from("nice demo"),
auther: String::from("Kevin"),
};
println!("{}", book.copy()); //book_name is nice demo,auther is Kevin
let jps = Jps {
title: String::from("demo_title"),
content: String::from("this is a content"),
auther: String::from("Kevin"),
};
println!("{}", jps.copy()); //title:demo_title,content:this is a content,auther:Kevin
}
我们在lib.rs中声明了trait和struct后在其他文件使用需要使用use
导入。包名为Cargo.toml中的[package]的name
实现trait的约束
可以在某个类型上实现某个trait的前提条件是:
- 这个类型或者trait是在本地create里定义的
我们无法为外部类型来实现外部的trait:
- 这个限制是程序属性的一部分(也就是一致性)
- 更具体的说是孤儿原则:之所以这样命名是因为父类型不存在
- 此规则确保其他人的代码不能破坏你的代码,反之亦然
- 如果没有这个规则,两个create可以为同一类型实现同一个trait,Rust就不知道用哪个实现了
默认实现
我们可以在trait中默认实现方法,我们在上面的例子中做一些改变:
lib.rs
pub trait CopyArticle {
// fn copy(&self) -> String;
fn copy(&self) -> String {
String::from("defualt")
}
}
//...
impl CopyArticle for Jps {}
//...
impl CopyArticle for Book {
//这里重写了copy方法
fn copy(&self) -> String {
format!("book_name is {},auther is {}", self.book_name, self.auther)
}
}
main.rs
//...
println!("{}", jps.copy()); //defualt
//...
println!("{}", book.copy()); //book_name is nice demo,auther is Kevin
默认实现的方法可以调用trait中其他的方法,即使这些方法没有默认实现
pub trait CopyArticle {
fn copy_dont(&self) -> String;
fn copy(&self) -> String {
format!("defualt,and more: {}", self.copy_dont())
}
}
//...
impl CopyArticle for Jps {
//需要实现没有默认实现的才能正常使用
fn copy_dont(&self) -> String {
format!(
"title:{},content:{},auther:{}",
self.title, self.content, self.auther
)
}
}
注意:无法从方法的重写实现里面调用默认的实现,比如这样:
pub trait CopyArticle {
fn copy(&self) -> String {
String::from("sss")
}
}
//...
impl CopyArticle for Jps {
fn copy(&self) -> String {
format!("yes more: {}", self.copy())//不允许这样做
}
}
Trait作为参数(类型)
比如我们现在要封装一个方法,在方法中要使用trait的方法,我们可以这样做:
使用impl Trait语法:适用于简单语法
pub trait CopyArticle {
fn copy(&self) -> String;
}
//...
fn a_func(item: impl CopyArticle) {
println!("{}", item.copy());
}
Trait bound语法(使用泛型):可用于复杂情况
//impl Trait语法
fn a_func1(item1: impl CopyArticle, item2: impl CopyArticle) {
println!("{},{}", item1.copy(), item2.copy());
}
//Trait bound语法
fn a_func2<T: CopyArticle>(item1: T, item2: T) {
println!("{},{}", item1.copy(), item2.copy());
}
实际上,impl Trait语法的Trait bound的语法糖
使用+
指定多个Trait bound:
fn a_func1(item1: impl CopyArticle + Display) {
println!("{}", item1.copy());
}
fn a_func2<T: CopyArticle + Display>(item1: T) {
println!("{}", item1.copy());
}
我们指定多个Trait bound可能会出现函数签名过于累赘,影响代码的可读性,比如像这样:
fn a_func1<T: CopyArticle + Display, U: Debug + Clone>(item1: T, item2: U) -> String {
format!("nice yes {}", item1.copy())
}
我们可以使用Trait bound使用where子句:
fn a_func2<T, U>(item1: T, item2: U) -> String
where
T: CopyArticle + Display,
U: Debug + Clone,
{
format!("nice yes {}", item1.copy())
}
这个时候我们发现,函数签名的可读性就非常好了。
其中,where子句在函数签名的返回类型后。
实现Trait作为返回类型
使用impl Trait语法
fn a_func2() -> impl CopyArticle {
Book {
auther: String::from("kevin"),
book_name: String::from("yes nice"),
}
}
但是,需要注意的是:返回的类型必须确定的同一类型,返回可能不同的类型会报错
下面的代码将会报错,因为:虽然它们都实现了CopyArticle
这个Trait,但是这个函数可能返回的类型是不同的类型,所以会报错。
//会报错
fn a_func2(boolean: bool) -> impl CopyArticle {
if boolean {
Book {
auther: String::from("kevin"),
book_name: String::from("yes nice"),
}
} else {
Jps {
auther: String::from("kevin"),
title: String::from("yes nice"),
content: String::from("ooo ooo ooo"),
}
}
}