Rust 泛型数据类型

泛型运行代码作用于抽象的类型

在函数定义中使用泛型

当使用泛型来定义函数时,将泛型放在函数签名中指定参数和返回值类型的地方。

首先我们来看两个函数:

fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> char {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest_i32(&number_list);
    println!("The largest number is {}", result);
    assert_eq!(result, 100);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest_char(&char_list);
    println!("The largest char is {}", result);
    assert_eq!(result, 'y');
}

largest_i32和largest_char其实实现了相同的功能,只是适用于不同的数据类型。我们可以通过泛型来消除重复代码。

fn largest<T>(list: &[T]) -> T {

我们首先需要给泛型命名,你可以适用任何合法标识符来命名泛型,习惯上我们使用T,在函数名后加尖括号T,并在参数和返回值需要的地方,加上泛型。
以上的代码可以理解为:函数largest拥有泛型参数T,它接收一个名为list的T值切片作为参数,并返回一个同样拥有类型T的值作为结果。

接下来,我们使用修改后的泛型代码,但是代码还是错误的,我们慢慢来修复它:

fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

报错信息如下:

$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `T`
 --> src/main.rs:5:17
  |
5 |         if item > largest {
  |            ---- ^ ------- T
  |            |
  |            T
  |
  = note: `T` might need a bound for `std::cmp::PartialOrd`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10`.

To learn more, run the command again with --verbose.

说的是std::cmp::PartialOrd是一个trait,我们将在下一节来讨论它。简单的说就是,并不是所有类型都是可排序的。我们可以通过实现std::cmp::PartialOrd这个trait来为类型实现比较功能。如何使用trait之后会讨论,我们先看看其他可能用到泛型参数的地方。

在结构体定义中使用泛型

依旧时在名称后加一个尖括号T。

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

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

注意,尖括号中只定义了一种泛型T,所以x和y无论类型是什么,他们的类型都是相同的。下面这种代码是无法通过编译的:

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

fn main() {
    let wont_work = Point { x: 5, y: 4.0 };
}

因为这里的x和y不是同一种类型。

如果需要将x和y实例化为不同类型,那么就需要声明两个泛型:

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

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

在枚举定义中使用泛型

典范:

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

在方法定义中使用泛型

方法定义泛型有些特殊,是要在impl后就定义泛型,point< T >。通过在impl之后将T声明为泛型,Rust能够识别出Point尖括号内的类型是泛型而不是具体类型。

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

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

泛型代码的性能问题

rust实现的泛型于具体类型的代码相比,在速度上没有差异。
因为rust执行了泛型代码的单态化:
在编译期间将泛型代码转换成所有可能的特定代码的过程。

trait(特征)被用来向rust编译器描述某些特定类型拥有的且能够被其他类型共享的功能,它使我们可以以一种抽象的方式来定义共享行为。我们还可以使用trait约束来将泛型参数指定为实现了某些特定行为的类型。 定 ...