Rust中如何复用代码?

705 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第19天,点击查看活动详情

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);
}