定义
trait
翻译过来叫“特征”,用于定义与其他类型共享的功能,类似于golang里面的inteface{}
- 抽象定义共享行为
trait bounds
指定泛型必须是拥有特定行为的类型
实现
pub trait Animal {
fn cry(&self) -> &String; // 叫声
fn food(&self) -> &String; // 食谱
}
pub struct Duck {
cry : String,
food: String,
}
pub struct Dog {
cry : String,
food: String,
}
impl Animal for Duck {
fn cry(&self) -> &String { // &self的作用域在方法外,方法调用结束后引用并不会被回收
return &self.cry;
}
fn food(&self) -> &String {
return &self.food;
}
}
impl Animal for Dog {
fn cry(&self) -> &String {
return &self.cry;
}
fn food(&self) -> &String {
return &self.food;
}
}
fn main() {
let duck = Duck { cry: "ga ga".to_string(), food: "eat fish".to_string() };
println!("鸭子叫声: {}, 主要食物:{}",duck.cry(),duck.food());
let dog = Dog { cry: "wang wang".to_string(), food: "eat shit".to_string() };
println!("狗叫声: {}, 主要食物:{}",dog.cry(),dog.food());
}
值得注意的是fn cry(&self) -> &String;
这个特征方法的定义,由于返回值是一个引用,那么这个引用必须是从入参中传入的,由此避免出现悬垂引用。入参引用的作用域在外层调用方法处,方法结束后不会被回收。
错误示例如下,Rust会检查,编译时报错
fn cry(&self) -> &String { // 引用2指向一个空地址,出现悬垂引用
&"ga ga".to_string()
} // 引用1被drop
作为参数传递
类似于Java中的多态
fn print_animal(animal: impl Animal) {
println!("叫声: {}, 主要食物:{}", animal.cry(), animal.food());
}
fn main() {
let duck = Duck { cry: "ga ga".to_string(), food: "eat fish".to_string() };
print_animal(duck);
let dog = Dog { cry: "wang wang".to_string(), food: "eat shit".to_string() };
print_animal(dog);
}
trait bound
和作为参数传递方式不同的是,trait bound
写法支持多个 trait 约束
// **********定义两个trait************
trait Cry {
fn cry(&self) -> &String;
}
trait Food {
fn food(&self) -> &String;
}
struct Bird {
cry: String,
food: String,
}
// **********实现两个trait************
impl Cry for Bird {
fn cry(&self) -> &String {
return &self.cry
}
}
impl Food for Bird{
fn food(&self) -> &String {
return &self.food
}
}
// 定义方法并声明约束:需要该类型实现了Cry+Food两个trait
fn print_info<T:Cry+Food>(obj:T){
println!("{}, {}",obj.cry(),obj.food())
}
fn main() {
let bird = Bird { cry: "渣渣叫".to_string(), food: "吃虫子".to_string() };
print_info(bird)
}
方式二:用where
关键字定义方法约束:trait bound
多的情况下更优雅,可读性更好
fn print_info<T>(obj: T)
where
T: Cry + Food,
{
println!("{}, {}", obj.cry(), obj.food())
}
提供默认实现
trait Info{
// 提供默认实现
fn get_name(&self) -> String{
return "Tom".to_string()
}
}
struct People{}
// 空实现,就能调用默认实现
impl Info for People{}
fn main() {
let people = People {};
println!("get name = {}",people.get_name())
}
作为返回值
一个错误的例子如下:
Dog
和Duck
两个结构体都分别实现了Animal
这个trait
- 按照其他语言的接口使用方法,我们想根据参数的不同返回不同类型的对象,预期能成功
但在Rust中不允许这么做,返回的类型必须是确定的,否则编译就会报错
enum Kind {
DogKind,
DuckKind,
}
fn get_animal(kind: Kind) -> impl Animal {
match kind {
Kind::DogKind => {
Dog { cry: "汪汪叫".to_string(), food: "吃骨头".to_string() }
}
Kind::DuckKind => {
Duck { cry: "嘎嘎叫".to_string(), food: "吃水草".to_string() }
}
}
}
fn main() {
let animal = get_animal(Kind::DogKind);
println!("{}", animal.food())
}
错误如下:期望返回的是Dog类型,但实际返回了Duck类型
error[E0308]: `match` arms have incompatible types
--> src/main.rs:137:13
|
132 | / match kind {
133 | | Kind::DogKind => {
134 | | Dog { cry: "汪汪叫".to_string(), food: "吃骨头".to_string() }
| | ------------------------------------------------------------- this is found to be of type `Dog`
135 | | }
136 | | Kind::DuckKind => {
137 | | Duck { cry: "嘎嘎叫".to_string(), food: "吃水草".to_string() }
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Dog`, found `Duck`
138 | | }
139 | | }
| |_____- `match` arms have incompatible types
正确的例子如下
fn get_dog_animal() -> impl Animal {
Dog { cry: "汪汪叫".to_string(), food: "吃骨头".to_string() }
}
fn main() {
let animal = get_dog_animal();
println!("{}", animal.food())
}
有条件的实现
对任何实现了特定trait的类型有条件的实现trait
- 定义 T 类型
- 如果 T 类型实现了 trait x,则为之实现 trait y
类似于其他语言中继承的概念
trait Cry {
fn cry(&self) -> &String;
}
trait PrintCry {
fn print_cry(&self);
}
// 如果 T 类型实现了 trait x,则为之实现 trait y
impl<T: Cry> PrintCry for T {
fn print_cry(&self) {
println!("打印: {}", self.cry())
}
}
struct Bird {
cry: String,
}
impl Cry for Bird {
fn cry(&self) -> &String {
return &self.cry;
}
}
fn main() {
let bird = Bird { cry: "渣渣叫".to_string() };
bird.print_cry();
}