泛型
多态:多态就像一个通用炮管,既可以发射普通炮弹,也可以发射制导炮弹,没有必要为每一种炮弹设计一个专用炮管。
泛型:泛型就是一种多态,目的在于提供编程的便利,简化代码。
fn add<T>(a: T, b: T) -> T {
a + b
}
fn main() {
println!("add i8: {}", add(2i8, 3i8));
println!("add i32: {}", add(20, 30));
println!("add f64: {}", add(1.23, 1.23));
}它无法过编,只是为了展示泛型的概念。
T 为泛型参数,名称任意,按惯例取 type 的首字母 T。
在使用泛型参数前,需要先声明:
fn largest<T>(list: &[T]) -> T这是一个泛型函数的签名,其作用为从元素类型为 T 的列表中找到最大的值。<T> 是对 T 的声明。
// not ok
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}这是一个错误的实现,编译器提示运算符 > 不能用于类型 T,并建议为 T 添加一个类型限制,使用 std::cmp::PartialOrd 特征对 T 进行限制,该特征的目的在于让类型实现可比较的功能。
// ok
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}这样修改就可以过编了。
对于最初的 a + b,该泛型 T 需实现 std::ops::Add<Output = T> 进行限制。
// ok
fn add<T: std::ops::Add<Output = T>>(a:T, b:T) -> T {
a + b
}泛型的使用
在结构体中使用泛型
struct Point<T> {
x: T,
y: T,
}结构体 Point 定义了一个坐标点,可以存放任何类型的坐标值:
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}使用说明:
- 类似于泛型函数的定义,在使用泛型参数前需要先声明,然后才能用
T代替具体的类型。 - 创建
Point实例时,同一个点的x和y必须类型相同。
// not ok
let p = Point{ x: 1, y: 2.0 }; // err: mismatched types当把 1 赋给 x 时,T 的类型就确定为 i32,随后 y 却接收了一个 f32 的值,因此出现错误。
如果希望 x 与 y 既能够类型相同、又能够类型不同,需要使用两种泛型参数:
// ok
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let p = Point{ x: 1, y: 2.0 };
}需要避免泛型的滥用,如果声明了一个 struct Foo<T, U, V, W, X>,就要考虑拆分结构体了。
在枚举中使用泛型
之前多次出现的 Option 用于枚举一个值存在与否:
enum Option<T> {
Some(T),
None,
}与之类似的枚举 Result 用于枚举一个值正确与否:
enum Result<T, E> {
Ok(T),
Err(E),
}- 如果函数正常运行,返回一个
Ok(T),T代表具体的返回类型。 - 如果运行错误,返回一个
Err(E),E代表错误类型。
例如,如果成功打开文件,返回 Ok(std::fs::File),否则返回 Err(std::io::Error)。
在方法中使用泛型
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}结构体 Point 的声明中就有泛型参数 T,其 impl 块就应声明为 impl<T> Point<T>,不过,块内的方法也可以使用其他的泛型类:
impl<T> Point<T> {
fn x<U>(&self) -> &U {
&self.x
}
}const 泛型
[i32; 2] 与 [i32; 3] 是不同的数组类型:
fn main() {
let arr1: [i32;2] = [1,2];
display_array(arr); // ok
let arr2: [i32; 3] = [1, 2, 3];
display_array(arr); // err: mismatched types
}
fn print_array(arr: [i32; 2]) {
println!("{:?}", arr);
}修改 print_array 使其能够打印任意长度的 i32 数组:
fn print_array(arr: &[i32]) { // 参数改为数组切片, 调用时传入引用
println!("{:?}", arr);
}进一步修改,使其能够打印任意类型的数组:
// 限制 std::fmt::Debug 表明 T 可以用在 println! 中
fn print_array<T: std::fmt::Debug>(arr: &[T]) {
println!("{:?}", arr);
}引用能满足打印不定长数组的需求,但如果需要的是数组本身呢?
const 泛型是针对值的泛型,可以解决这个问题:
fn print_array<T: std::fmt::Debug, const N: usize>(arr: [T; N]) {
println!("{:?}", arr);
}现在,参数 arr 的类型为 [T; N],T 是基于类型的泛型参数,用以指代 i32、f32 等类型;而 N 是基于值的泛型参数,用以指代 1、4 等值,在这里指代数组的长度,这就是 const 泛型,它基于的值类型为 usize。
泛型的性能
泛型不会对运行时性能产生影响,只会拖慢编译速度、增大文件大小。
特征
在实现一个文件系统时,将其与底层存储解耦很重要,但不可能为每种情况单独实现一套代码。将文件的各种操作抽象出来,就是特征(trait)的概念,它类似于其他语言的“接口”。
之前就使用过特征:
#[derive(Debug)],在自定义的类型上自动派生Debug特征,以便用println!("{:?}", x)打印出来。- 使用
std::ops::Add特征限制泛型T,使T能够进行加法操作。
特征定义了一组可以被共享的行为,只要实现某特征,就可以使用这组行为。
特征的定义
如果若干类型具有相同的行为,就可以定义一个特征,并为这些类型实现。
例如,现在有文章 Post 与博客 Blog 两种内容载体,而文章的内容都可以“总结”,这个“总结”的行为就是共享的,由此可以定义特征 Summary:
pub trait Summary {
fn summarize(&self) -> String;
}trait:关键字,声明一个特征。Summary:特征名。summarize:特征的方法,只有签名,以;结尾。
为类型实现特征
为 Post 与 Blog 实现 Summary 特征:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct Post {
pub title: String,
pub author: String,
pub content: String,
}
pub struct Blog {
pub username: String,
pub content: String,
}
impl Summary for Post {
fn summarize(&self) -> String {
format!("title: {}, author: {}", self.title, self.author)
}
}
impl Summary for Blog {
fn summarize(&self) -> String {
format!("user: {}, content: {}", self.username, self.content)
}
}fn main() {
let post = Post {
title: "Rust Course".to_string(),
author: "Sunface".to_string(),
content: "Rust is the best lang!".to_string()
};
println!("{}", post.summarize());
let blog = Blog {
username: "Sunface".to_string(),
content: "learn Rust!".to_string()
};
println!("{}", blog.summarize());
}孤儿规则:如需为类型 A 实现特征 T,那么二者至少有一个的定义位于当前作用域内。
- 可以为
Post实现标准库中的Display特征。 - 可以在当前包中为标准库的
String类型实现Summary特征。
默认实现:可以在特征定义中为方法进行默认实现。
pub trait Summary {
fn summarize(&self) -> String {
String::from("This is a default impl for summarize()")
}
}之后,为结构实现 Summary 特征时可以选择性地重载 summarize。
默认实现也可以调用特征中的其他方法,被调用方法可以有、也可以没有默认实现。
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("author: {}", self.summarize_author())
}
}特征作为函数参数
pub fn notify(item: &impl Summary) {
println!("NOTE: {}", item.summarize());
}item 的类型为 impl Summary 类型的不可变引用,意为“实现了 Summary 特征”。
notify() 可以接收任何实现了 Summary 特征的类型作为参数,例如 Post 的实例,并可以在内部调用 Summary 的方法。
特征约束:impl Trait 是一种语法糖,完整的写法:
pub fn notify<T: Summary>(item: &T) {
println!("NOTE: {}", item.summarize());
}特征约束对于复杂的场景很有用,例如,notify() 现在接收两个 &impl Summary 参数:
pub fn notify(item1: &impl Summary, item2: &impl Summary)如果我们希望限制 item1、item2 为同一实际类型(例如两个 Post 实例,而不允许一个 Post 实例与一个 Blog 实例),语法糖就无能为力了,但是特征约束就可以做到:
pub fn notify<T: Summary>(item1: &T, item2: &T)泛型 T 说明 item1 与 item2 的类型相同,T: Summary 说明 T 需要实现 Summary 特征。
多重约束:约束参数实现多个特征。
pub fn notify(item: &(impl Summary + Display))pub fn notify<T: Summary + Display>(item: &T)where 约束:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32
// 用 where 写作
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug函数返回中的 impl Trait
通过 impl Trait 指示函数返回一个实现了 Trait 特征的类型:
fn returns_summarizable() -> impl Summary {
Blog {
username: String::from("Sunface"),
content: String::from("learn Rust!"),
}
}impl Trait 返回类型通常用于实际类型极其复杂的情况,因为 Rust 要求必须指明所有类型。例如,返回一个迭代器时,其具体类型极其复杂,就可以将返回类型写为 impl Iterator。
不过,这种抽象的返回类型只能指代一个具体的类型,例如:
// err
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
Post {
...
}
} else {
Weibo {
...
}
}
}这是不行的,因为 impl Summary 不可以指代两个不同的类型,编译器提示 “`if` and `else` have incompatible types”。
要实现返回不同的类型,需使用特征对象(见下一章)。
修复 largest()
运算符 > 是标准库中特征 std::cmp::PartialOrd 的默认方法,将 largest() 签名修改为:
fn largest<T: PartialOrd>(list: &[T]) -> T仍然无法过编,编译器提示 T 没有实现 Copy 特性,需要增加约束:
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T