[rust]trait

8 阅读3分钟

定义

trait翻译过来叫“特征”,用于定义与其他类型共享的功能,类似于golang里面的inteface{}

  1. 抽象定义共享行为
  2. 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())
}

作为返回值

一个错误的例子如下:

  1. DogDuck两个结构体都分别实现了Animal这个trait
  2. 按照其他语言的接口使用方法,我们想根据参数的不同返回不同类型的对象,预期能成功

但在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

  1. 定义 T 类型
  2. 如果 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();
}