类似于 cpp,Rust 中也有泛型(generics),用于避免重复的代码。此外,还可以指定 traits 来限制泛型必须实现某个功能。
Generics
我们可以在函数、结构体、枚举类型中使用泛型。
函数中的泛型
我们定义了以下函数:
fn largest(list: &[T]) -> &T
这个函数可以用于求各类型数组的最大值,避免了为每个类型实现同样的函数。
fn largest(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item
}
}
return largest;
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
println!("The largest number is {}", largest(&number_list));
let char_list = vec!['y', 'm', 'a', 'q'];
println!("The largest char is {}", largest(&char_list));
}
但是,由于不是所有类型都能使用 >
进行比较,编译时会报错:
error[E0369]: binary operation `>` cannot be applied to type `&T`
--> src/main.rs:4:17
|
4 | if item > largest {
| ---- ^ ------- &T
| |
| &T
|
help: consider restricting type parameter `T`
|
1 | fn largest(list: &[T]) -> &T {
| ^^^^^^^^^^^^^^^^^^^^^^
对于这个错误,我们会在之后讲 trait 时修复。
结构体中的泛型
struct Point {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
枚举类型中的泛型
enum Result {
Ok(T),
Err(E),
}
泛型对 Runtime 性能无影响
泛型并不影响程序在运行时的速度,因为 Rust 会在编译时为使用泛型的代码填充具体类型。
用 Option
举例说明。如果我们定义了:
let integer = Some(5);
let float = Some(5.0);
那么在编译期间,编译器会读取所有使用泛型的代码,并扩展成具体类型。
在这,我们使用了 i32
和 f64
,因此代码会扩展为:
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
因为运行时,泛型已经被扩展为普通的函数,因此没有运行时开销。
Trait
trait 定义了一些类型的共有的功能,可以利用 trait 指定泛型必须有某种行为。
trait 和某些语言中的“接口”类似。
定义 trait
我们可以使用如下方式定义 trait:
pub trait Summary {
fn summarize(&self) -> String;
}
注意这里使用了 pub
表明这个 trait 对该 crate 外是可见的。此外,我们只需要提供函数签名,而不需要实现。
实现 trait
实现这个 Summary
trait 的类型需要提供这个 summarize
函数的实现,使用 impl...for...
语法,例如:
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)
}
}
不能为一个外部的类型实现外部的 trait
要求 trait 或者类型至少有一方是自己定义在 crate 里的。
例如,我们无法为 Vec
实现 Display
trait。因为 Vec
类型和 Display
trait 都是在标准库中定义。
这样的限制是为了保证“一致性”,即让一个 crate 无法影响其依赖包的行为。否则,两个 crates 可以为同一个类型实现同一个 trait,Rust 无法决定使用哪个实现。
trait 的默认实现
在定义 trait 时,我们也可以给出默认实现。默认实现的方法可以调用 trait 中的其他方法,即使没有默认实现:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
这样在实现 trait 时,summarize_author
是必须实现的,而summarize
可以采用默认实现:
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// use default implementation for summarize()
}
用 trait 做参数类型
我们可以用 trait 而非实际类型来作为函数的参数。如:
fn notify(item: &T) {
println!("Breaking news! {}", item.summarize());
}
对于要实现多个 trait 的类型,我们可以用 +
连接:
fn notify(item: &T) {
println!("Breaking news! {}", item.summarize());
}
利用 trait bounds 修复 largest
函数
我们只需要指明 T
是实现了 std::cmp::PartialOrd
trait 的(T: PartialOrd
):
fn largest(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item
}
}
return largest;
}
利用 trait bounds 为特定类型实现方法
当我们使用泛型类型时,通过 trait bound 可以为某些 trait 的类型实现方法。
如果某个方法是对所有类型的,可以写在这个 scope 里:
impl Pair {
}
如果仅对满足 Display
和 PartialOrd
的类型生效,可以写在这个 scope 里:
impl Pair {
}
例如:
struct Pair {
first: T,
second: T,
}
impl Pair {
fn new(x: T, y: T) -> Self {
return Self{first: x, second: y};
}
}
impl Pair {
fn display_greater(&self) {
let greater = if self.first > self.second {&self.first} else {&self.second};
println!("The greater element is {}", greater);
}
}
fn main() {
let pair = Pair::new(3, 4);
pair.display_greater();
}
达到的效果是,对所有类型的 Pair,都有 new
方法,但是仅对实现了 Display
和 PartialOrd
trait 类型的 Pair,有 display_greater
方法。
此外,我们还可以为任意类型实现 trait,语法是:
impl function for T {
}
这个在 Rust 标准库中非常常用。例如:
/// # Panics
///
/// In this implementation, the `to_string` method panics
/// if the `Display` implementation returns an error.
/// This indicates an incorrect `Display` implementation
/// since `fmt::Write for String` never returns an error itself.
#[stable(feature = "rust1", since = "1.0.0")]
impl ToString for T {
// A common guideline is to not inline generic functions. However,
// removing `#[inline]` from this method causes non-negligible regressions.
// See , the last attempt
// to try to remove it.
#[inline]
default fn to_string(&self) -> String {
use fmt::Write;
let mut buf = String::new();
buf.write_fmt(format_args!("{}", self))
.expect("a Display implementation returned an error unexpectedly");
buf
}
}