trait用来共享行为。与其它语言类比,就是相当于接口。

对于某一个行为,如果多种类型都需要实现它,那么就将其定义为一个trait trait只定义不实现。

pub trait Summary {
	fn summarize(&self) -> String;
}

为类型实现trait

trait定义的行为终究还是需要实现的。

pub struct NewArticle {
	pub headline: String,
	pub location: String,
	pub author: String,
	pub content: String,
}
 
impl Summary for NewArticle {
	fn summarize(&self) -> String {
		format!("{}, by {} ({})", self.headline, self.author, self.location);
	}
}
pub struct Tweet {
	pub username: String,
	pub content: String,
	pub reply: bool,
	pub retweet: bool,
}
 
impl Summary for Tweet {
	fn summarize(&self) -> String {
		format!("{}: {}", self.username, self.content);
	}
}

Rust实现trait有一个规则,叫做孤儿规则:只有当trait或类型定义在我们自己的库里的时候,才能够为该类型实现对应的trait。这一规则的目的很简单,就是为了防止破坏别人的代码。

  • 能够为别人的类型实现自己的trait。
  • 能够为自己的类型实现别人的trait。

具有默认实现的trait

前面将的trait只有行为的定义,但是没有行为的实现,要求类型实现自己的特定类型的行为。 不过有时候,有的行为对于绝大部分类型都是一样的实现,通过trait的默认实现可以避免为这些类型的实现重复代码。

pub trait Summary {
	fn summarize(&self) -> String {
		String::from("(Read more...)")
	}
}

如果类型需要这种默认实现,就直接声明实现了trait即可,不需要再为默认的行为实现了。如

impl Summary for NewArticle {}

如果需要特定的实现,再向前面所述的那样为类型实现trait即可。

trait作为参数接收具有统一行为的不同类型

这一点的用法是与其它语言的接口一致的。既要函数的参数能够接收不同的类型,但是又希望接收的类型是有限制的。

例如,定义一个notify函数来调用实现了Summary trait类型的summarize方法。

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

但是trait约束其实是一种语法糖,其完整写法为:

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

使用加号可以指定多个trait约束。例如要求同时具有格式化函数:

pub fn notify(item: impl Summary + Display)

泛型约束也可以使用加号

pub fn notify<T: Summary + Display>(item: T)

当有多个泛型参数,并且每个泛型参数都有很多约束的时候,将这些约束还按照上面的写法就会让签名变得难以理解,因此可以使用where关键字将约束提取出来,保持函数签名的可读性。

fn some_function<T, U>(t: T, u: U) -> i32 
	where T:Display+Clone,
		  U:Clone+Debug
{}

返回实现了trait的值

trait约束可以作为函数参数,当然也可以作为函数返回值。

fn returns_summarizable() -> impl Summary {
	Tweet{
		username: String::from("horse_ebooks"),
		content: String::from("of course, as you probably already know, people"),
		reply: false,
		retweet: false,
	}
}

这一功能被闭包迭代器高度依赖,可以创建出只有编译器才知道的签名长到难以想要的类型。

fn returns_summarizable(switch: bool) -> impl Summary {
	if switch {        
		NewsArticle {            
			headline: String::from("Penguins win the Stanley Cup Championship!"),            
			location: String::from("Pittsburgh, PA, USA"),            
			author: String::from("Iceburgh"),            
			content: String::from("The Pittsburgh Penguins once again are the best            hockey team in the NHL."),        
		}    
	} else {        
		Tweet {            
		username: String::from("horse_ebooks"),            
		content: String::from("of course, as you probably already know, people"),
		reply: false,            
		retweet: false,        
		}    
	}
}

使用trait约束来有条件地实现方法

通过带泛型参数的impl代码块中使用trait约束,可以为实现了指定trait的类型实现方法。

use std::fmt::Display;
struct Pair<T> {    
	x: T,    
	y: T,
}
impl<T> Pair<T> {    
	fn new(x: T, y: T) -> Self {        
		Self {
	        x,            
	        y,        
	    }    
	}
}
impl<T: Display + PartialOrd> Pair<T> {    
	fn cmp_display(&self) {        
		if self.x >= self.y {            
			println!("The largest member is x = {}", self.x);        
		} else {            
			println!("The largest member is y = {}", self.y);        
		}    
	}
}

如上,类型Pair<T>都会实现new函数,但是只有实现了DisplayPartialOrd的trait的内部类型T参会实现cmp_display函数。

例如标准库为所有实现了Display trait的类型实现了ToString trait。

impl <T: Display> ToString for T {
	//略
}

使用trait和trait约束,可以在使用泛型消除重复代码的同时,向编译器表明自己期望放心所具有的功能。


tags: trait约束 泛型