5分钟速读之Rust权威指南(十七)

199 阅读3分钟

泛型

函数可以使用参数中未知的具体值来执行相同的代码,函数也可以使用泛型参数而不是i32或String之类的具体类型,使用过TS的前端同学接触起来就很快了

泛型函数

泛型在使用的时候需要在函数名称后面声明,然后在函数参数类型位置和返回值类型位置使用,例如下面求数组中的最大值:

fn largest<T>(list: &[T]) -> T {
  let mut largest = list[0];
  for &item in list.iter() {
    // 这里会报错,因为T可能是任意类型,
    // 并不是所有类型都可以比较,可以先忽略,后面会介绍如何解决
    if item > largest {
      largest = item
    }
  }
  largest
}
println!("{}", largest(&['a','b','c','d']));
println!("{}", largest(&[1,2,3,3]));

泛型枚举

枚举值也可以包含泛型,泛型声明放在枚举名称后面,例如下面的内置Option和Result枚举:

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

泛型结构体

结构体同样支持泛型,泛型参数同样在结构体名称后面:

#[derive(Debug)]
struct Point<T, U> {
  x: T,
  y: U,
};

// 传入类型参数
let i32_p = Point::<i32, i32> {
  x: 100,
  y: 100,
};

// 不填参数,编译器可以自动推断类型:Point<f32, f32>
let f32_p = Point {
  x: 100.1,
  y: 100.1
};

泛型结构体中的方法

在方法中使用,可以单独为Point<i32, i32>实例而不是所有的Point<T, U>泛型实例来实现方法,表示具有Point<i32, i32>类型的结构体才允许调用x2方法:

impl Point<i32, i32> {
  fn x(&self) -> &i32 {
    &self.x
  }
}
println!("{}", i32_p.x()); // 100
println!("{}", f32_p.x()); // 报错, Point<f32, f32>结构体上不存在x方法

如果想要在这个结构体所有类型都是用这个方法,可以实现泛型方法:

// 实现泛型方法:
impl<T, U> Point<T, U> {
  fn x2(&self) -> &T {
    &self.x
  }
}

// 注意,声明<T, U>必须紧跟着impl关键字,以便能够在实现方法时指定类型Point<T, U>。
// 通过在impl之后将T, U声明为泛型,rust能够识别出Point尖括号内的类型是泛型而不是具体类型。
let str_p = Point {
  x: "100px",
  y: "100px",
};
println!("{}", str_p.x2()); // 100px

结构体定义中的泛型参数并不一定与方法签名上使用的泛型类型参数一样:

// 声明泛型的就近原则:在结构体的泛型要声明在impl中,而方法的泛型要声明在方法后边
impl<T, U> Point<T, U> {
  // mixup方法使用与结构体定义不同的泛型参数
  fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
    return Point {
      x: self.x,
      y: other.y
    }
  }
}

let p1 = Point {
  x: 1,
  y: 2,
};

let p2 = Point {
  x: "hello",
  y: 'c',
};

println!("{:?}", p1.mixup(p2));
// Point { x: 1, y: 'c' }

什么时候使用泛型

当意识到代码拥有多个结构体或枚举定义,且仅仅只有值类型不同时,就可以通过使用泛型来避免重复代码。

泛型代码的性能问题

rust会在编译时执行泛型代码的单态化(monomorphization)。单态化是一个在编译期将泛型代码转换为特定代码的过程,它会将所有使用过的具体类型填入泛型参数从而得到有具体类型的代码:

let i = Option::Some(1);
let f = Option::Some(1.2);

// 编译成:
enum Option_i32 {
  Some(i32),
  None
}
enum Option_f32 {
  Some(f32),
  None
}
let i = Option_i32::Some(1);
let f = Option_f32::Some(1.2);

由于rust会将每一个实例中的泛型代码编译为特定类型的代码,所以我们不用为泛型的使用付出任何运行时的代价。当运行泛型代码时,其运行效果和我们手动重复每个定义的运行效果一样。单态化使rust的泛型代码在运行时极其高效。