以下内容为本人学习过程中的记录笔记,其中可能存在不准确或错误,欢迎勘误及指正

泛型的作用主要体现在提高代码的复用能力。在官方文档中,泛型被定义为具体类型或其他属性的抽象代替,在实际使用时可以将泛型理解为一种模板占位符,编译器会在编译时 执行单态化(monomorphization)操作,即将泛型类型替换为具体的类型

在结构体中使用泛型

在结构体中使用泛型一般存在两种情况,其一为定义结构体时,其二为在方法定义时

在定义中使用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 注意:这里的泛型类型要求x和y的类型必须一致
struct Point<T> {
x: T,
y: T,
}

fn main() {
let rec = Point { x: 12.5, y: 9.2 };

let squ = Point { x: 9, y: 9 };

println!("x: {}, y: {}", rec.x, rec.y);
println!("x: {}, y: {}", squ.x, squ.y);
}

在方法中使用泛型

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
// 注意:这里的泛型类型要求x和y的类型可以不一致,也可以一致
struct Point<T, U> {
x: T,
y: U,
}

// 此处意思为针对泛型类型T和U实现的方法而不是针对某特定类型实现方法,即所有类型都可以调用该方法
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,
}
}
}

// 此处意思为仅针对数据类型为i32时实现方法,当结构体中字段为非i32类型时无法调用该方法
impl Point<i32, i32> {
fn mix_i32(self, other: Point<i32, i32>) -> Point<i32, i32> {
Point {
x: self.x,
y: other.y,
}
}
}

fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c'};

let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

同时,由上面的例子可注意到在结构体中的泛型类型参数可以和方法的泛型类型参数不同

在枚举中使用泛型

在枚举中使用泛型和结构体类似,一般用在使枚举的变体持有泛型数据类型。比如Option枚举和Result枚举

1
2
3
4
5
6
7
8
9
10
11
// Option枚举
enum Option<T> {
Some(T),
None,
}

// Result枚举
enum Result<T, E> {
Ok(T),
Err(E),
}

在函数中使用泛型

以下是一个最简单的在函数中使用泛型的例子,但在实际项目中,我们通常需要对泛型进行限制以保证函数得到的参数能满足后续相关的功能要求 ,这时需要使用Trait对泛型参数进行约束

1
2
3
4
5
6
7
8
9
10
fn main() {
let a = 1;
let b = 2.0;
println!("{}", back(a));
println!("{}", back(b));
}

fn back<T>(item: T) -> T {
item
}

泛型的Trait约束

在Trait部分我们说,可以通过Trait对传入或返回参数的类型进行约束。在使用泛型类型参数时可以配合Trait使用,以实现对传入参数的类型进行约束

简单情况

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
34
35
36
use std::fmt::Debug;

trait Shape {
fn perimeter(&self) -> f64;
fn area(&self) -> f64;
}

#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}

impl Shape for Rectangle {
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
fn area(&self) -> f64 {
self.width * self.height
}
}

fn main() {
let rec = Rectangle {
width: 12.0,
height: 20.0,
};

trait_param(&rec);
}

// 类型T必须实现Shape trait和Debug trait
fn trait_param<T: Shape + Debug>(item: &T) {
println!("{:?}", item);
println!("{}", item.area());
}

使用where关键字

在上面的例子中,我们实现了对函数传入参数进行约束。但在实际项目中,泛型参数或Trait约束较多会造成代码不易阅读,我们可以使用where关键字来使代码更加直观。上面例子其实可以看做使用where关键字的语法糖

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
use std::fmt::Debug;

trait Shape {
fn perimeter(&self) -> f64;
fn area(&self) -> f64;
}

struct Rectangle {
width: f64,
height: f64,
}

impl Shape for Rectangle {
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
fn area(&self) -> f64 {
self.width * self.height
}
}

#[derive(Debug)]
struct Circle {
radius: f64,
}

impl Shape for Circle {
fn perimeter(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}

fn main() {
let rec = Rectangle {
width: 12.0,
height: 20.0,
};

let cir = Circle { radius: 11.0 };

trait_param(&rec, &cir);
}

fn trait_param<T, U>(rec: &T, cir: &U)
// 使用where对多个泛型参数设置多个Trait约束
where
// 类型T必须实现Shape trait
T: Shape,
// 类型U必须实现Shape trait和Debug trait
U: Shape + Debug,
{
println!("{}", rec.area());
println!("{:?}", cir);
}