数据类型
是对同一类数据的抽象,而泛型
是对具有一组相同行为的数据类型的抽象。Rust 使用 trait
来描述这一组相同的行为,简单的说 trait
就是一组函数的集合。Rust
中的trait
类似于其他语言中的常被称为接口
(interfaces)的功能。
假如有两个函数,它们的功能是查找slice
中最大值并返回,函数体中的采用的算法是一样的,主要不同的是参数类型。如下所示:
fn largest_i32(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
fn largest_char(list: &[char]) -> char {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
遇到这种情况我们就可以将重复部分抽取出来,用泛型
代替参数中的数据类型。
// T 是泛型类型,它代表所有的数据类型。
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
// 这样“任何”类型的`slice`都可以使用该函数了,而不用单独为每个类型都定义一个函数
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("The largest number is {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {}", result);
}
但是,上面的示例是不能编译通过的,因为存在两个问题:
T
类型的值,但并不是所有的数据类型都能使用这个运算符。list
参数的类型有可能是没有实现 Copy trait
的,这意味着我们可能不能将 list[0]
的值移动到 largest
变量中。如果要完善这个函数,需要为T
类型指定相应的trait
,来进行限定。
可以使用 <>
语法来定义拥有一个或多个泛型参数类型字段的结构体。
示例一:指定一个泛型参数
struct Point<T> {
x: T,
y: T,
}
// x 和 y 的类型必须相同
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
示例二:指定两个泛型参数
struct Point<T, U> {
x: T,
y: U,
}
// x 和 y 的类型可以不同
fn main() {
let both_integer = Point { x: 5, y: 10 };
let both_float = Point { x: 1.0, y: 4.0 };
let integer_and_float = Point { x: 5, y: 4.0 };
}
与结构体类似,枚举中也可以使用泛型。
示例一:标准库中的Option<T>
enum Option<T> {
Some(T),
None,
}
示例二:标准库中的Result<T, E>
enum Result<T, E> {
Ok(T),
Err(E),
}
示例:在 Point<T>
结构体上实现方法 x,它返回 T 类型的字段 x 的引用
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
除了使用泛型,也可以为特定的数据类型定义一个方法。
示例:
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
使用trait
可以帮助我们确保类型拥有期望的行为,对数据类型进行限定。
使用 trait
关键字定义 trait
。
示例:
pub trait Summary {
// 可以有多个方法:一行一个方法签名且都以分号结尾。
fn summarize(&self) -> String;
}
示例:
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
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)
}
}
在trait
中可以提供默认的实现,当某个特定的类型实现trait
时,可以选择保留或者重载这个默认的实现。
示例:
// Summary trait 的定义,带有一个 summarize 方法的默认实现
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
// NewsArticle 类型使用这个默认实现
impl Summary for NewsArticle {}
// 重载这个默认实现
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
trait
代表的是一组函数,因此可以用它来指定那些实现了特定trait
的数据类型。
示例一:
// 任何实现了 Summary 的类型都可以使用这个函数
// 可以认为是在泛型的基础上,添加了 trait 限定。这种语法称之为 trait bound。
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
// 这是对上面语法形式的简写
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
示例二:通过 + 指定多个 trait
,数据类型必须实现指定的多个 trait
// 使用 impl 关键字
pub fn notify(item: impl Summary + Display) {}
// 使用 trait bound 语法
pub fn notify<T: Summary + Display>(item: T) {}
示例三:通过 where
简化 trait bound
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {}
// 上面的函数可以简化成下面这种形式
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{}
示例四:返回实现了 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,
}
}