使用泛型的位置
不仅仅是函数的参数可以指定泛型,任何需要指定数据类型的地方,都可以使用泛型来替代具体的数据类型,以此来表示此处可以使用比某种具体类型更为通用的数据类型。
而且,可以同时使用多个泛型,只要将不同的泛型定义为不同的名称即可。例如,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,那么可能有必要在定义泛型类型时加上泛型限制。