泛型是一个编程语言不可或缺的机制。 C++ 语言中用"模板"来实现泛型,而 C 语言中没有泛型的机制,这也导致 C 语言难以构建类型复杂的工程。 泛型机制是编程语言用于表达类型抽象的机制,一般用于功能确定、数据类型待定的类,如链表、映射表等。
fn largest<T>(list: &[T]) ->T {...}
泛型函数
返回类型
fn main() {
let a = vec![10, 80, 2022, 36, 47];
let largest = largest(&a);
println!("The largest ele is {}", largest);
}
fn largest(list: &[i32]) -> i32 {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
上面这段代码是求一个集合中最大的元素,我们定义的集合是一个 i32
类型,但是这时如果我们要传入 f32
或者字符型,还用同样的逻辑判断函数的话,是会报错的,这时我们就需要用到泛型。
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
我们声明了一个泛型 T
,但是这样是会编译报错的,因为不是所有类型 T 都可以进行大小比较,只有实现了下面的 std::cmp::PartialOrd
的 trait 才能进行大小比较,所以要对 T 进行约束。
➜ ~/Code/rust/pattern git:(master) ✗ cargo run
Compiling pattern v0.1.0 (/home/cherry/Code/rust/pattern)
error[E0369]: binary operation `>` cannot be applied to type `T`
--> src/main.rs:10:17
|
10 | if item > largest {
| ---- ^ ------- T
| |
| T
|
help: consider restricting type parameter `T`
|
7 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`.
error: could not compile `pattern` due to previous error
但是把 std::cmp::PartialOrd
这个 trait 加上又会报其他错误,这里在后面会进行介绍。
可以使用多个泛型的类型参数,但是也不要有太多的类型,否则代码可读性将会下降。例如:
struct Point<T, U> {
x: T,
y: U,
}
fn test01() {
let integer = Point{x: 2022, y: 6.1};
}
可以让枚举的变体持有泛型数据类型,例如:Option, Result
enum Option<T> {
Some(T),
None
}
enum Result<T, E> {
Ok(T),
Err(E),
}
fn test01() {
let integer = Point{x: 2022, y: 61};
println!("{}", integer.x());
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
注意
impl<T> Point<T>
impl Point<f32>
struct 中的泛型参数可以和方法的泛型参数不同
impl<T, U> Point<T, U> {
fn x(&self) -> &T {
&self.x
}
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V,W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y
}
}
}
fn test02() {
let p1 = Point{x: 61, y: 85};
let p2 = Point{x: "Hello", y: "Rust"};
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
上面实现的结构体方法实际上是将第一个 Point 中的 x 和第二个 Point 的 y 结合起来形成一个新的 Point。
使用泛型的代码和使用具体类型的代码运行速度是一样的
单态化(monomorphization)
在编译时将泛型替换为具体类型的过程
let ingeter = Some(5);
let float = Some(5.0);
enum Option_i32 {
Some(i32),
None
}
enum Option_f32 {
Some(f32),
None
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
Trait的定义:把方法签名放在一起,来定义实现某种目的所必需的一组行为。
;
结尾实现该 trait 的类型必须提供具体的方法实现
pub trait Summary {
fn summarize(&self) -> String;
}
impl Xxxx for Tweet {...}
文件 lib.rs
:
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)
}
}
文件 main.rs
:
use trait_demo::Summary;
use trait_demo::Tweet;
fn main() {
let tweet = Tweet {
username: String::from("Cherry_ICT"),
content: String::from("People in Shanghai are free today..."),
reply: false,
retweet: false
};
println!("Get 1 new tweet: {}", tweet.summarize());
}
实现的功能很简单,不做具体解释了。
默认实现
默认实现的方法可以调用 trait 中的其他方法,即使这些方法没有默认实现,但是注意,无法从方法的重写实现中调用默认实现。
pub trait Summary {
fn summarize(&self) -> String {
format!("(Read more from {} ...)", self.summarize_author())
}
fn summarize_author(&self) -> String;
}
在 trait 中可以有方法的默认实现,在默认实现的基础上,类型可以对该 trait 进行重载。同样,在 trait 中默认实现的方法可以实现 trait 中其他方法。
刚刚 trait 例子的完整代码如下:
lib.rs
:
pub trait Summary {
fn summarize(&self) -> String {
format!("(Read more from {} ...)", self.summarize_author())
}
fn summarize_author(&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)
}
fn summarize_author(&self) -> String {
format!("@{}", self.author)
}
}
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)
}
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
main.rs
:
use trait_demo::Summary;
use trait_demo::Tweet;
use trait_demo::NewsArticle;
fn main() {
let tweet = Tweet {
username: String::from("Cherry_ICT"),
content: String::from("People in Shanghai are free today..."),
reply: false,
retweet: false
};
println!("Get 1 new tweet: {}", tweet.summarize());
let news = NewsArticle {
headline: String::from("WWDC will be held in June 7th"),
location: String::from("USA"),
author: String::from("Tim Cook"),
content: String::from("The Apple will take us a lot of devices."),
};
println!("You receive a news: {}", news.summarize());
}
最终输出结果为:
➜ ~/Code/rust/trait_demo git:(master) ✗ cargo run
Compiling trait_demo v0.1.0 (/home/cherry/Code/rust/trait_demo)
Finished dev [unoptimized + debuginfo] target(s) in 0.33s
Running `target/debug/trait_demo`
Get 1 new tweet: Cherry_ICT: People in Shanghai are free today...
You receive a news: WWDC will be held in June 7th, by Tim Cook (USA)
+
指定多个 trait boundTrait bound 使用 where 子句
在方法签名后指定 where 子句
pub trait Summary {}
pub struct NewsArticle {}
impl Summary for NewsArticle {}
pub struct Tweet {}
impl Summary for Tweet {}
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
这是采用 impl Trait 的语法,这里的 notify 方法要求传入的参数可以是 NewsArticle
类型或者是 Tweet
类型,也就是要求参数要实现 Summary
这个 trait,从而使用 summarize 这个方法。
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
这是采用 Trait bound 的写法,下面这个例子讲展示出这种写法的优势:
pub fn notify<T: Summary>(item1: T, item2: T) {
println!("Breaking news! {}", item.summarize());
}
当有多个参数时,采用这种写法可以使得代码相对简洁一些。
使用 +
指定多个 trait bound:
pub fn notify1(item: impl Summary + Display) {
println!("Breaking news! {}", item.summarize());
}
pub fn notify<T: Summary + Display>(item: T) {
println!("Breaking news! {}", item.summarize());
}
然而如果一个函数中参数过多,那么整个函数声明就会变得非常长,不太直观,可读性差,这里可以使用 where 子句来指定 trait 的约束:
pub fn notify2<T: Summary + Display, U: Clone + Debug>(a: T, b: U) -> String {
format!("Breaking news! {}", a.summarize())
}
这个例子中函数签名太长,不够直观,采用 where 子句可以使得更加直观:
pub fn notify3<T, U>(a: T, b: U) -> String
where
T: Summary + Display,
U: Clone + Debug,
{
format!("Breaking news! {}", a.summarize())
}
impl trait 语法
注意:impl Trait 只能返回确定的同一种类型,返回可能不同类型的代码会报错
pub fn notify4(flag: bool) -> impl Summary {
if flag {
NewsArticle {...}
} else {
Tweet {...}
}
}
这样的话这个函数便没有了确定的返回类型,这样便会报错。
我们再来看一下之前的代码。解决如下:
fn largest<T: PartialOrd>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
之前我们说过,实际上比较大小的运算符是实现了 std::cmp::PartialOrd
这样一个 trait,因此我们需要指定实现这个 trait 的泛型才能进行大小比较。
但是这样改完后又会出现一个问题:
➜ ~/Code/rust/pattern git:(master) ✗ cargo run
Compiling pattern v0.1.0 (/home/cherry/Code/rust/pattern)
error[E0508]: cannot move out of type `[T]`, a non-copy slice
--> src/main.rs:10:19
|
10 | let mut largest = list[0];
| ^^^^^^^
| |
| cannot move out of here
| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
| help: consider borrowing here: `&list[0]`
error[E0507]: cannot move out of a shared reference
--> src/main.rs:11:18
|
11 | for &item in list {
| ----- ^^^^
| ||
| |data moved here
| |move occurs because `item` has type `T`, which does not implement the `Copy` trait
| help: consider removing the `&`: `item`
Some errors have detailed explanations: E0507, E0508.
For more information about an error, try `rustc --explain E0507`.
error: could not compile `pattern` due to 2 previous errors
报错原因是:无法从 list 中移除 T,因为没有实现 Copy trait,建议采用借用
因为上面两个 vector 中的元素分别为整型和字符型,这两种类型有确定的大小并且都是存储在栈中,因此都实现了 Copy trait,于是在 T 的 trait 约束中再加上 Copy 即可:
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
但是如果将 vec 中元素类型改为 String,那么又会报错,因为 String 是存储在堆中,没有实现 Copy trait,但是实现了 Clone trait
let str_list = vec![String::from("Hello"), String::from("World")];
let largest = get_max_ele(&str_list);
我们将 T 加上 Clone 约束,去掉 Copy 约束:
fn get_max_ele<T: PartialOrd + Clone>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
这样又会出现错误:
error[E0508]: cannot move out of type `[T]`, a non-copy slice
--> src/main.rs:21:23
|
21 | let mut largest = list[0];
| ^^^^^^^
| |
| cannot move out of here
| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
| help: consider borrowing here: `&list[0]`
是因为这里 list[0] 是字符串切片,是一个借用,没有所有权,因此一个借用给一个变量赋值,这个借用对应的类型必须要实现 Copy trait。因此在 list 前面加上引用,并且将 item 也设为引用,最后返回 &T。
fn get_max_ele<T: PartialOrd + Clone>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
若要最后还是返回 T,则可以使用 clone 方法:
fn get_max_ele<T: PartialOrd + Clone>(list: &[T]) -> T {
let mut largest = list[0].clone();
for item in list {
if item > &largest {
largest = item.clone();
}
}
largest
}
在使用泛型类型参数的 impl 块上使用 Trait bound,我们可以有条件的为实现了特定 Trait 的类型来实现方法
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 cmd_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
为满足 Trait Bound 的所有类型上实现 Trait 叫做覆盖实现 (blanket implementations)
impl<T: fmt::Display> Tostring for T {}
含义为:为实现了 Display trait 的类型实现 ToString trait,而 ToString 中实现了 to_string 方法。
例如 let s = 3.to_string();