trait 相似于其他编程言语中的常被称为接口(interface)的功能,但还是有一些差异的。 trait 告诉 Rust 编译器某个特定类型具有或许与其他类型同享的功能。能够经过 trait 以一种笼统的办法界说同享的行为。能够运用 trait bounds 指定泛型是任何具有特定行为的类型。

简单的了解,trait 便是 Rust 中的接口,界说了某个类型运用这个接口时的行为。运用 trait 能够束缚多个类型之间同享的行为,在运用泛型编程时还能束缚泛型有必要契合 trait 规定的行为。

界说 trait

假如不同的类型具有相同的行为,那么就能够界说一个 trait,然后为这些类型完成该 trait。界说 trait 便是把一些办法组合在一起,目的是界说一个完成某些目标所必需的行为和调集。

trait 是界说了一系列办法的接口:

pub trait Summary {
    //trait里面的办法只需求写声明即可
    fn summarize_author(&self) -> String;
    // 界说为默许完成的办法,其他类型就无需再完成该办法
    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}
  • 界说了一个名称为Summary的 trait,它包含summarize_authorsummarize两个办法供给的行为。
  • trait 里面的办法只需求写上声明即可,完成交给详细的结构体来做,当然,办法也能够有默许完成的,这儿的summarize办法就包含默许完成,并且这儿在summarize办法默许完成内部还调用了不包含默许完成的 summarize_author办法。
  • Summary trait的两个办法的参数中都包含关键self,与结构体办法相同,self 用作 trait 办法的第一个参数。

注:实际上selfself: Self的简写,&selfself: &Self的简写,&mut selfself &mut Self的简写。
Self代表的是当前完成了 trait 的类型,例如有一个类型 Foo 完成了 Summary trait,则完成办法时中的 Self 便是Foo。

pub trait Summary {
    fn summarize_author(&self) -> String;
    fn summarize(&self) -> String {
        format!("@{}宣布了帖子...", self.summarize_author())
    }
}
pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}
pub struct Post { 
    pub title: String, // 标题 
    pub author: String, // 作者
    pub content: String, // 内容 
}
impl Summary for Post {
   fn summarize_author(&self) -> String { 
     format!("{}宣布了贴子", self.author)
   } 
   fn summarize(&self) -> String { 
     format!("{}宣布了贴子:{}", self.author, self.content)
   } 
} 
impl Summary for Tweet { 
   fn summarize_author(&self) -> String { 
     format!("{}宣布了微博", self.username)
   } 
   fn summarize(&self) -> String { 
     format!("@{}宣布了微博:{}", self.username, self.content)
   } 
}
fn main() {
   let tweet = Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
   println!("{}",  tweet.summarize())
}

关于 trait 完成与界说的位置,有一条非常重要的原则(孤儿原则):假如你想要为类型A完成traitT,那么A或许T至少有一个是在当前作用域中界说的!

这个规则能够确保其他人编写的代码不会损坏你的代码,也确保了你不会莫名其妙损坏他人的代码。

运用 trait 作为函数参数

Trait 能够用作函数参数,这儿先界说一个函数,运用 trait 用作函数参数:

pub fn notify(item: &impl Summary) { // trait 参数
    println!("Breaking news! {}", item.summarize());
}

参数的意思便是完成了 Summary trait 的 item 参数。能够运用任何完成了Summarytrait 的类型作为该函数的参数,一起在函数体内,还能够调用该 trait 的办法。

Trait 束缚 (trait bound)

上面运用的impl Trait实际上仅仅一个语法糖,其完好书写方法如下,形如T: Summary被称为 trait 束缚。

pub fn notify<T: Summary> (item: &T) { // trait 束缚
    println!("Breaking news! {}", item.summarize());
}

关于比较杂乱的运用场景,特征束缚能够让咱们具有更大的灵活性和语法表现能力,例如一个函数承受两个impl Summary的参数:

pub fn notify(item1: &impl Summary, item2: &impl Summary) {} // trait 参数
pub fn notify<T: Summary>(item1: &T, item2: &T) {}  // 泛型 T 束缚:阐明 item1 和 item2 有必要具有同样的类型,一起阐明 T 有必要完成 Summary trait

经过+指定多个 trait bound

除了单个束缚条件,还能够指定多个束缚条件,例如让参数完成多个 trait:

pub fn notify(item: &(impl Summary + Display)) {}  // 语法糖方法
pub fn notify<T: Summary + Display> (item: &T) {}  // trait bound 完好方法

经过where简化 trait bound

当特征束缚变得很多时,函数的签名就会变得很杂乱,这时假如运用where能够对其做一些方法上的改善:

// 当出现多个泛型类型时,过多的trait bound会导致函数签名难以阅览
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 { ... }
// 运用 where 做简化,使得函数名、参数列表和回来值类型都离得很近,看起来跟没有那么多 trait bounds 的函数很像
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
      U: Clone + Debug {
      ....
}

运用 trait 束缚有条件地完成办法或 trait

trait 束缚作为参数,能够让咱们在指定类型+指定 trait 的条件下完成办法,使函数能承受来自多个不同类型的参数。
例如:

fn notify(summary: impl Summary) {
    println!("notify: {}",  summary.summarize())
}
fn notify_all(summaries: Vec<impl Summary>) {
    for summary in summaries {
        println!("notify: {}",  summary.summarize())
    }
}
fn main() {
   let tweet = Weibo {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
   let tweets = vec![tweet];
   notify_all(tweets);
}

函数的summary参数的类型是impl Summary,而不是详细的类型。这样该参数就支撑任何完成了Summary trait的类型。

当希望具有一个值并只关怀它的类型是否完成了特定 trait 而不是其详细类型的时候,能够运用智能指针 Box 和关键字 dyn 组合的 trait 目标(注:后续将独自章节学习 trait 目标)。

fn notify(summary: Box<dyn Summary>) {
    println!("notify: {}",  summary.summarize())
}
fn notify_all(summaries: Vec<Box<dyn Summary>>) {
    for summary in summaries {
        println!("notify: {}",  summary.summarize())
    }
}
fn main() {
   let tweet = Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
   let tweets: Vec<Box<dyn Summary>> = vec![Box::new(tweet)];
   notify_all(tweets);
}

在泛型中运用trait

最终来看一下在泛型编程中运用 trait 束缚泛型类型的行为。

上面界说 trait 比方中 notify 函数fn notify(summary: impl Summary),关于 summary 参数类型,指定的是 impl 关键字加 trait 名称,而不是详细的类型。 实际上impl Summary是泛型编程中 Trait Bound 的语法糖,所以上面的impl trait 代码能够改写为以下方法:

fn notify<T: Summary>(summary: T) {
    println!("notify: {}",  summary.summarize())
}
fn notify_all<T: Summary>(summaries: Vec<T>) {
    for summary in summaries {
        println!("notify: {}",  summary.summarize())
    }
}
fn main() {
   let tweet = Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   };
   let tweets = vec![tweet];
   notify_all(tweets);
}

函数回来中的 impl Trait

能够经过impl Trait来阐明一个函数回来了一个类型,该类型完成了某个 trait:

fn returns_summarizable() -> impl Summary {
    Tweet {
       username: String::from("haha"),
       content: String::from("the content"),
       reply: false,
       retweet: false,
   }
}

这种impl Trait方法的回来值只能是单一类型的 trait,假如多个类型的 trait 就会报错,比方:

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        Tweet { ... }  // 此处不能回来两个不同的类型
    } else {
        Post { ... }  // 此处不能回来两个不同的类型
    }
}

上面的代码会报错,因为回来了两个不同的类型 trait:TweetPost。假如想要完成回来不同的类型,需求运用trait 目标,关于这部分后续将独自章节学习。

fn returns_summarizable(switch: bool) -> Box<dyn Summary> {
    if switch {
        Box::new(Tweet { ... }) // trait 目标
    } else {
        Box::new(Post { ... })  // trait 目标
    }
}

总结

在 Rust 设计目标中,零成本笼统是非常重要的一条,它让 Rust 具备高级言语表达能力的一起,又不会带来功能损耗。零成本的基石是泛型与 trait,它们能够在编译期把高级语法编译成与高效的底层代码,然后完成运行时的高效。

trait 是以笼统的办法界说同享的行为,trait bound 是对函数参数或许回来值界说类型束缚,比方impl SuperTraitT: SuperTrait,trait 和 trait bound 让咱们运用泛型类型参数来削减重复,并仍然能够向编译器清晰指定泛型类型需求具有哪些行为。因为咱们向编译器供给了 trait bound 信息,它就能够检查代码中所用到的详细类型是否供给了正确的行为。综上,trait 用途简单的归纳为:

  • 笼统行为:相似接口,是对类型共性的同一性笼统,界说共同行为
  • 类型束缚:类型的行为被 trait 限定在更有限的范围内

参阅

  • kaisery.github.io/trpl-zh-cn/…
  • course.rs/basic/trait…
  • rustwiki.org/zh-CN/refer…
  • rustwiki.org/zh-CN/rust-…