类似于 cpp,Rust 中也有泛型(generics),用于避免重复的代码。此外,还可以指定 traits 来限制泛型必须实现某个功能。
Generics
我们可以在函数、结构体、枚举类型中使用泛型。
函数中的泛型
我们定义了以下函数:
fn largest<T>(list: &[T]) -> &T
这个函数可以用于求各类型数组的最大值,避免了为每个类型实现同样的函数。
fn largest<T>(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<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
| ^^^^^^^^^^^^^^^^^^^^^^
对于这个错误,我们会在之后讲 trait 时修复。
结构体中的泛型
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
枚举类型中的泛型
enum Result<T, E> {
Ok(T),
Err(E),
}
泛型对 Runtime 性能无影响
泛型并不影响程序在运行时的速度,因为 Rust 会在编译时为使用泛型的代码填充具体类型。
用 Option<T>
举例说明。如果我们定义了:
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<T>
实现 Display
trait。因为 Vec<T>
类型和 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<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
对于要实现多个 trait 的类型,我们可以用 +
连接:
fn notify<T: Summary + Display>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
利用 trait bounds 修复 largest
函数
我们只需要指明 T
是实现了 std::cmp::PartialOrd
trait 的(T: PartialOrd
):
fn largest<T: PartialOrd>(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<T> Pair<T> {
}
如果仅对满足 Display
和 PartialOrd
的类型生效,可以写在这个 scope 里:
impl<T: PartialOrd + Display> Pair<T> {
}
例如:
struct Pair<T> {
first: T,
second: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
return Self{first: x, second: y};
}
}
impl<T: PartialOrd + Display> Pair<T> {
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<T: Trait1> 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<T: fmt::Display + ?Sized> ToString for T {
// A common guideline is to not inline generic functions. However,
// removing `#[inline]` from this method causes non-negligible regressions.
// See <https://github.com/rust-lang/rust/pull/74852>, 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
}
}
网友评论