泛型与trait-Rust

编程语言都有高效处理重复概念的工具,比如打印信息的函数或者方法再接收参数的时候我们往往希望该函数可以接受不同类型的参数进行打印,而不是为每一种类型都编写一个打印函数,这就要求打印函数需要能够接受多种类型的参数

Option

HashMap<K, V>

Result<T, E>

代码去重

泛型允许我们使用一个可以代表多种类型的占位符来替换特定类型,以此来减少代码冗余。

寻找列表中最大值的小程序

1
2
3
4
5
6
7
8
9
10
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
}

如果我们需要对比两个集合那么代码就会变成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let mut largest = &number_list[0];
for number in &number_list {
if number > largest {
largest = number;
}
}
println!("The largest number is {largest}");
}

这个时候代码会变得比较繁琐,我么可以简化为如下形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn largest(list:&[i32])->&i32{
let mut largest_num=&list[0];
for item in list{
if item > largest_num{
largest_num=item;
}
}
largest_num
}


fn main() {
let number_list1 = vec![34, 50, 25, 100, 65];
let largest1=largest(&number_list1);
println!("The number_list1 largest number is {largest1}");

let number_list2 = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let largest2=largest(&number_list2);

println!("The number_list2 largest number is {largest2}");
}

如果我们需要比较数字和字符该如何选择呢,函数的参数类型该如何设置呢?

泛型数据类型

观察如下两个函数,他们的逻辑没有区别,只是参数类型不同,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fn largest_i32(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn largest_char(list: &[char]) -> &char {//函数通过比较标量值判断大小
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}

fn largest<T>(list: &[T]) -> &T函数 largest 有泛型类型 T。它有个参数 list,其类型是元素为 T 的

slice。largest 函数会返回一个与 T 相同类型的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {//请注意这些代码还不能编译
largest = item;
}
}
largest
}
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}");
}

struct与泛型

X,Y需要保持类型的一致,因为只有一个泛型T,可以再加一个泛型让两个属性类型不一致

image-20241221160130863

方法与泛型

如下实现方法针对于Point类型,只有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  struct Point<T>{
x:T,
y:T,
}

impl<T> Point<T> {
fn x(&self)->&T{
&self.x
}
}

fn main() {
let s1=Point{x:5,y:6};
println!("p.x{}",s1.x());

}

trait

特质,特征的意思,类似接口的概念

如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为(trait)了,一般为定义在lib下的无具体实现代码的函数的集合体

我们想要创建一个名为 aggregator 的多媒体聚合库用来显示可能储存在 NewsArticle 或 Tweet实例中的数据摘要。为了实现功能,每个结构体都要能够获取摘要,这样的话就可以调用实例的 summarize 方法来请求摘要。

lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
7
8
9
10
11
12
13
use value::{Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),

reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}

结构体想要实现trait需要满足一些条件,这个类型或者trait必须在本地creat里定义

无法为外部类型实现外部trait,防止不会破坏代码

trait作为参数

使用 trait 来接受多种不同类型的参数

我们需要一个函数既可以接受NewsArticle结构体,也可以接收Tweet结构体

第一种方法impl trait方法,用于简单情况

1
2
3
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}

第二种方法trait bound用于复杂情况

1
2
3
pub fn notify<T:Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}

加号指定多个trait,意思是该参数实现了多个trait

1
2
3
pub fn notify<T:Summary + Display>(item: T) {
println!("Breaking news! {}", item.summarize());
}

where子句

1
2
3
4
5
6
7
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作为返回类型

1
2
3
4
5
6
7
8
9
10
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,
}
}

需要注意的是trait作为返回类型的时候只能返回同一种类型,比如可能返回两种实现了一个trait的类型的时候就会报错

trait bound有条件地实现方法

标准库的tostring

1
2
3
impl<T: Display> ToString for T {//T表示任意类型,任意类型都有ToString方法只要你实现了Display trait
// --snip--
}

实验

main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use std::fmt::Display;
pub struct Pair<T> {
pub x: T,
pub y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}

fn main() {

// 示例1:使用满足Display和PartialOrd的类型i32创建Pair实例,可调用cmp_display方法
let pair_i32 = Pair::new(5, 3);
pair_i32.cmp_display();

// 示例2:使用不满足Display和PartialOrd的自定义类型MyType创建Pair实例,无法调用cmp_display方法
struct MyType;
let pair_my_type = Pair::new(MyType, MyType);
// 下面这行代码会报错,因为MyType没有实现Display和PartialOrd,所以Pair<MyType>不能调用cmp_display方法
// pair_my_type.cmp_display();

}