Rust进阶[part2]_泛型

Rust进阶[part2]_泛型

泛型概述

在定义函数时运用泛型,可将原本函数签名里明确指定参数和返回值类型的部分,用泛型来替代。这种方式能增强代码的适应性,为函数调用者赋予更多功能,还能避免代码重复。

fn add<T>(a:T, b:T) -> T{
  a + b
}

不过,并非所有的T类型都能进行相加操作,此时会提示错误:
genertic_type.rs(5, 9): consider restricting type parameter 'T' with trait 'Add': ': std::ops::Add<Output = T>'
修正后的代码如下:

fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
    a + b
}

使用场景

在函数定义中使用泛型

fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

在结构体中使用泛型

struct Point<T> {
    x: T,
    y: T,
}

// 实例化泛型结构体
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };

在枚举里面使用泛型

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

// 实例化泛型枚举
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("Division by zero".to_string())
    } else {
        Ok(numerator / denominator)
    }
}

impl之后声明泛型T

泛型参数可以和结构体定义中声明的泛型参数不一样。

struct Point<T, U> {
    x: T,
    y: 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,
        }
    }
}

// 示例用法
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c' };
let p3 = p1.mixup(p2);

const泛型

// 定义一个固定大小的数组类型
struct ArrayBuffer<T, const N: usize> {
    data: [T; N],
    len: usize,
}

impl<T, const N: usize> ArrayBuffer<T, N>
where
    T: Default + Copy,
{
    fn new() -> Self {
        ArrayBuffer {
            data: [Default::default(); N],
            len: 0,
        }
    }
}


// 创建一个可以存储10个i32的缓冲区
let buffer: ArrayBuffer<i32, 10> = ArrayBuffer::new();

where子句可以直接写在泛型参数后面,例如:

// 写法1:使用where子句
fn new_array_buffer<T, const N: usize>() -> ArrayBuffer<T, N>
where
    T: Default + Copy,
{ ... }

// 写法2:直接在泛型参数后指定约束
fn new_array_buffer<T: Default + Copy, const N: usize>() -> ArrayBuffer<T, N> { ... }

泛型代码的性能

Rust通过在编译时对泛型代码进行单态化来保证效率。

当代码运行时,其执行效率和手写每个具体定义的重复代码一样。正是这个单态化过程,使得Rust泛型在运行时极为高效。

// 泛型代码
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
    a + b
}

// 单态化后的代码示例
fn add_i32(a: i32, b: i32) -> i32 {
    a + b
}

fn add_f64(a: f64, b: f64) -> f64 {
    a + b
}