使用泛型的位置

不僅僅是函數的參數可以指定泛型,任何需要指定數據類型的地方,都可以使用泛型來替代具體的數據類型,以此來表示此處可以使用比某種具體類型更為通用的數據類型。

而且,可以同時使用多個泛型,只要將不同的泛型定義為不同的名稱即可。例如,HashMap類型是保存鍵值對的類型,它的key是一種泛型類型,它的值也是一種泛型類型。它的定義如下:


#![allow(unused)]
fn main() {
// 使用了三個泛型,分別是K、V、S,並且泛型S的默認類型是RandomState
struct HashMap<K, V, S = RandomState> {
  base: base::HashMap<K, V, S>,
}
}

實際上,Struct、Enum、impl、Trait等地方都可以使用泛型,仍然要注意的是,需要在類型的名稱後或者impl後先聲明泛型,才能使用已聲明的泛型。

下面是一些簡單的示例。

Struct使用泛型:


#![allow(unused)]
fn main() {
struct Container_tuple<T> (T)
struct Container_named<T: std::fmt::Display> {
  field: T,
}
}

例如,Vec類型就是泛型的Struct類型,其官方定義如下:


#![allow(unused)]
fn main() {
pub struct Vec<T> {
    buf: RawVec<T>,
    len: usize,
}
}

Enum使用泛型:


#![allow(unused)]
fn main() {
enum Option<T> {
  Some(T),
  None,
}
}

impl實現類型的方法時使用泛型:


#![allow(unused)]
fn main() {
struct Container<T>{
  item: T,
}

// impl後的T是聲明泛型T
// Container<T>的T對應Struct類型Container<T>
impl<T> Container<T> {
  fn new(item: T) -> Self {
    Container {item}
  }
}
}

Trait使用泛型:


#![allow(unused)]
fn main() {
// 表示將某種類型T轉換為當前類型
trait From<T> { 
  fn from(T) -> Self; 
}
}

某數據類型impl實現Trait時使用泛型:


#![allow(unused)]
fn main() {
use std::fmt::Debug;

trait Eatable {
  fn eat_me(&self);
}

#[derive(Debug)]
struct Food<T>(T);

impl<T: Debug> Eatable for Food<T> {
  fn eat_me(&self) {
    println!("Eating: {:?}", self);
  }
}
}

注意,上面impl時指定了T: Debug,它表示了Food<T>類型的T必須實現了Debug。為什麼不直接在定義Struct時,將Food定義為struct Food<T: Debug>而是在impl Food時才限制泛型T呢?

通常,應當儘量不在定義類型時限制泛型的範圍,除非確實有必要去限制。這是因為,泛型本就是為了描述更為抽象、更為通用的類型而存在的,限制泛型將使得類型變得更具體化,適用場景也更窄。但是在impl類型時,應當去限制泛型,並且遵守缺失什麼功能就添加什麼限制的規範,這樣可以使得所定義的方法不會過度泛化,也不會過度具體化。

簡單來說,儘量不去限制類型是什麼,而是限制類型能做什麼。

另一方面,即使定義struct Food<T: Debug>,在impl Food<T>時,也仍然要在impl時指定泛型的限制,否則將編譯錯誤


#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Food<T: Debug>(T);
impl<T: Debug> Eatable for Food<T> {}
}

也就是說,如果某個泛型類型有對應的impl,那麼在定義類型時指定的泛型限制很可能是多餘的。但如果沒有對應的impl,那麼可能有必要在定義泛型類型時加上泛型限制