【图书介绍】《Rust编程与项目实战》-CSDN博客
《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)
Rust编程与项目实战_夏天又到了的博客-CSDN博客
7.6.1 什么是泛型编程
C/C++、Rust都是强类型语言,在对数据进行处理时,必须明确数据的数据类型。但是很多时候,比如链表这种数据结构,可以是整型数据的链表,也可以是其他类型,可能会写出重复的代码,只是数据类型不同而已。泛型编程是一种编程风格,其中的算法以尽可能抽象的方式编写,而不依赖于将在其上执行这些算法的数据形式。泛型这个词并不是通用的,在不同的语言实现中,具有不同的命名。在Java/Kotlin/C#中称为泛型,在ML/Scala/Haskell中称为Parametric Polymorphism,而在C++中被叫作模板,比如最负盛名的C++中的STL。任何编程方法的发展一定有其目的,泛型也不例外。泛型的主要目的是加强类型安全和减少强制转换的次数。
所以,为了简化代码,我们将类型抽象成一种“参数”,数据和算法针对这种抽象的类型来实现,而不是具体的类型,当需要使用时再具体化、实例化。
7.6.2 在函数中使用泛型
泛型可以在函数中使用,将泛型放在函数的签名中,在其中指定参数的数据类型和返回值。当函数包含类型为T的单个参数时,语法如下:
fn function_name<T>(x:T)
// body of the function.
上面的语法包含两部分:
- <T>:给定的函数是一种类型的泛型。
- (x : T):x是类型T。
当函数包含多个相同类型的参数时,代码如下:
fn function_name<T>(x:T, y:T)
// body of the function.
当函数包含多个类型的参数时,代码如下:
fn function_name<T,U>(x:T, y:U)
// Body of the function.
下面举一个泛型的例子:
//不使用泛型
//针对整型数据
fn findmax_int(list : &[i32]) -> i32 {
let mut max_int = list[0];
for &i in list.iter() {
if i > max_int {
max_int = i;
}
}
max_int
}
//针对char数据
fn findmax_char(list : &[char]) -> char {
let mut max_char = list[0];
for &i in list.iter() {
if i > max_char {
max_char = i;
}
}
max_char
}
fn main() {
let v_int = vec![2, 4, 1, 5, 7, 3];
println!("max_int: {}", findmax_int(&v_int));
let v_char = vec!['A', 'C', 'G', 'B', 'F'];
println!("max_char: {}", findmax_char(&v_char));
}
运行结果如下:
max_int: 7
max_char: G
可以看到两个函数基本上是一样的。下面采用泛型的方式来简化代码:
fn find_max<T : PartialOrd + Copy> (list : &[T]) -> T {
let mut max = list[0];
for &i in list.iter() {
if i > max {
max = i;
}
}
max
}
fn main() {
let v_int = vec![2, 4, 1, 5, 7, 3];
println!("max_int: {}", find_max(&v_int));
let v_char = vec!['A', 'C', 'G', 'B', 'F'];
println!("max_char: {}", find_max(&v_char));
}
成功运行,运行结果如下:
max_int: 7
max_char: G
其实泛型是一个比较复杂的概念,可能大家还没体会到泛型的好处,我们再来看一个实际开发中经常会碰到的场景。
话说项目经理总是善变的,有一天项目经理告诉我,替客户计算一个圆形的面积。客户要求很简单,半径只会是u8类型。我写了代码如下:
fn area_u8(r: u8) -> u8 {
r * r
}
fn main() {
println!("{}", area_u8(3));
}
第二天项目经理又来了,说客户说的不对,半径在某种情况下还会是u16类型。于是我又添加了一个函数:
fn area_u8(r: u8) -> u8 {
r * r
}
fn area_u16(r: u16) -> u16 {
r * r
}
fn main() {
println!("{}", area_u8(3));
println!("{}", area_u16(10));
}
但第三天、第四天,项目经理又跑来说半径还会是u32、u64类型,甚至还可能是浮点数。我到底要写多少个函数才行!我意识到是时候叫出“超级飞侠”了。不对,是泛型了。泛型,顾名思义,就是广泛的类型,在Rust中,通常使用<T>表示,当然,不一定是T,也可以是A、B、C……
使用泛型并不容易,在这个例子中,我感受到了Rust编译器的强大。我的第一版程序如下:
fn area<T>(r: T) -> T {
r * r
}
fn main() {
println!("{}", area(3));
println!("{}", area(3.2));
}
然后编译器告诉我:
error[E0369]: cannot multiply `T` by `T`
--> main.rs:2:7
|
2 | r * r
| - ^ - T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn area<T: std::ops::Mul<Output = T>>(r: T) -> T {
| +++++++++++++++++++++++++++
error: aborting due to previous error
不能对两个T类型的数做乘法!那我该怎么办?幸亏有泛型的特性与特性绑定。我这样修改:
fn area<T: std::ops::Mul<Output = T> + Copy>(r: T) -> T {
r * r
}
fn main() {
println!("{}", area(3));
println!("{}", area(3.2));
}
终于可以得到正确结果了,运行结果如下:
9
10.240000000000002
回过头来解释一下刚才的过程。泛型指定的是任意类型,但并不是所有类型都能进行乘法运算。因此,我们需要对泛型加以限制。这被称为特性绑定,或泛型约束,意思是只有满足条件(实现了某个特性)的泛型才被允许传到函数中来。
上面的写法无疑使得第一行很长,可读性不好,为此Rust设计了where子句,用来实现泛型 约束:
fn area<T>(r: T) -> T
where T: std::ops::Mul<Output = T> + Copy
{
r * r
}
fn main() {
println!("{}", area(3));
println!("{}", area(3.2));
}
运行结果如下:
9
10.240000000000002
7.6.3 在结构体中使用泛型
这下足足过了1个月,我都没见到项目经理的身影,直到有一天,项目经理笑意满满地来到我的工位,说上次的程序写得太棒了,客户发现不管什么时候,我的程序都能正常工作。客户对我们公司非常肯定,决定再给我们一个新的项目:计算长方形的面积,此类项目前景非常好,为了便于扩展,最好能抽象成结构体。于是我一气呵成:
use std::ops::Mul; // 这么写可以简化代码
struct Rect<T> // 为结构体添加泛型
{
width: T, // 宽和高都是泛型
height: T
}
impl<T> Rect<T> { // 为泛型实现方法,impl后也要添加<T>
fn area(&self) -> T
where T: Mul<Output = T> + Copy { // 泛型约束
self.height * self.width
}
}
fn main() {
// 整型
let rect1 = Rect{width:3, height:4};
println!("{}", rect1.area());
// 浮点型
let rect2 = Rect{width:3.5, height:4.3};
println!("{}", rect2.area());
}
运行结果如下:
12
15.049999999999999