Rust中如何复用代码?
我们在编写代码的时候需要考虑代码的复用性,通常情况下我们会使用提取函数和使用泛型来对代码进行复用
提取函数消除重复
重复代码
假如我们现在要比较出一个vector中的最大值,我们会这样写:
let l = vec![20, 44, 13, 22, 77, 8];
let mut largest = l[0];
for num in l {
if largest < num {
largest = num;
}
}
println!("the largest number is {}", largest); //the largest number is 77
然后我们现在又要在另外一个Vector中挑出它的最大值:
let l = vec![20, 44, 13, 22, 77, 8];
let mut largest = l[0];
for num in l {
if largest < num {
largest = num;
}
}
println!("the largest number is {}", largest_num); //the largest number is 77
let l = vec![200, 404, 103, 202, 77, 80];
let mut largest = l[0];
for num in l {
if largest < num {
largest = num;
}
}
println!("the largest number is {}", largest); //the largest number is 404
我们发现有重复的代码(2-7和11-16),对于重复的代码:
- 容易出错
- 需求变更时需要在多处进行修改
为了消除重复,我们可以提取函数
fn largest_number(list: &[i32]) -> i32 {
let mut largest_num = list[0];
for &num in list {
if largest_num < num {
largest_num = num;
}
}
largest_num
}
fn main() {
let list = vec![20, 44, 13, 22, 77, 8];
let largest = largest_number(&list);
println!("the largest number is {}", largest); //the largest number is 77
let list = vec![200, 404, 103, 202, 77, 80];
let largest = largest_number(&list);
println!("the largest number is {}", largest); //the largest number is 404
}
其中,传入的list类型为&[i32]
实际上它是一个切片(我们必须传入引用,因为Rust不知道它的长度)。&num类型为i32,num类型为&i32。&num实际上进行了一个解构。我们如果不使用&
,也可以在后面使用*
进行解引用:
for num in list {
if largest_num < *num {
largest_num = *num;
}
}
消除重复的步骤
- 识别重复代码
- 提取重复代码到函数体中,并在函数签名中指定函数的输入和返回值
- 将重复的代码使用函数调用进行替代
泛型
泛型:提高代码复用能力,也就是说可以处理重复代码的问题
泛型是具体类型或其它属性的抽象代替:
- 可以理解为:你使用泛型编写代码时不是最终的代码,而是一种模板,里面有一些 “占位符”。
- 编译器在编译时将“占位符”替换为具体的类型(这个过程叫单态化)
例如:fn largest<T>(list:&[T])->T{...}
这个T被称为类型参数:
- 通常情况下很短,一般为一个字母,比如T(type的缩写)
- 在Rust中使用CamelCase大驼峰命名法
在函数定义中的泛型
在上面的例子中的比较大小只能用于i32类型,我们使用泛型来将其能用作比较字母的大小:
fn the_largest<T>(list: &[T]) -> T {
let mut largest = l[0];
for &num in l {
if largest < num {//这里会报错,因为并不是所有类型都支持比较,我们需要给泛型指定Trait,这里先不管
largest = num;
}
}
largest
}
fn main() {
let arr = [1, 3, 5, 7, 9];
let l = vec![20, 44, 13, 22, 77, 8];
let largest = the_largest(&list);
println!("the largest number is {}", largest); //the largest number is 77
let l = vec!['a', 's', 'e', 'b'];
let largest = the_largest(&list);
println!("the largest number is {}", largest); //the largest number is 404
}
上面的函数会报错,因为T没有实现std::cmp::PartialOrd
这一Trait(接口interface),暂时先不管这个。
Struct定义中的泛型
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 1, y: 2 }; //Point<i32>
let float = Point { x: 1.0, y: 2.0 }; //Point<f64>
}
多个类型参数:
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let float_int = Point { x: 1.0, y: 2 }; //Point<f64, i32>
}
如果太多类型参数,代码的可阅读性会变差,需要重组为多个更小的单元
Enum中使用泛型
可以让枚举的变体持有泛型数据类型,例如我们之前用到的Option\<T>,Result\<T,E>
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
方法定义中的泛型
为struct或enum实现方法的时候,可在定义中使用泛型
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
impl Point<i32> {
fn x1(&self) -> &i32 {
&self.x
}
}
如果impl是根据Point\<T>来实现的(在类型T上实现方法),我们需要在impl后加\<T>。如果这个T是一个确切的类型,比如i32,我们就不需要加泛型T
另外,struct里的泛型类型参数可以和方法的泛型类型参数不同
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mix<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 1, y: 2.3 };
let p2 = Point { x: 'a', y: "demo" };
let p3 = p1.mix(p2);
println!("x:{},y:{}", p3.x, p3.y); //x:1,y:demo
}
在上面的例子中,我们Point的类型参数为T,U,impl实现的也是T,U。而我们的mix函数的泛型的类型参数为V,W。
上面mix方法的意思为接收一个<T,U>类型的Point,接收另一个Point,不过这里是占位符为V,W(类型参数)。然后返回的Point的x为self的x。而y为另一个Point的y。类型为:`<自身x的类型,other中y的类型>
`
泛型代码的性能
使用泛型的代码和使用具体类型的代码运行速度是一样的。
这是因为Rust在编译时会执行单态化(monomorphization)的过程:
- 在编译时将泛型类型替换为具体类型的过程
fn main() {
let integer = Some(5);
let floater = Some(5.0);
}
上面的代码会被编译为这样(编译为具体的类型):
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let floater = Option_f64::Some(5.0);
}