使用泛型的位置
不僅僅是函數的參數可以指定泛型,任何需要指定數據類型的地方,都可以使用泛型來替代具體的數據類型,以此來表示此處可以使用比某種具體類型更為通用的數據類型。
而且,可以同時使用多個泛型,只要將不同的泛型定義為不同的名稱即可。例如,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,那麼可能有必要在定義泛型類型時加上泛型限制。