泛化类型和函数功能,以扩大其适用范围,大大减少代码重复。采用泛型意味着仔细地指定泛型类型具体化时,什么样的具体类型是合法的。泛型最简单和常用的用法是用于类型参数。
结构体
结构体泛型,struct GenVal<T>{}``struct GenValTuple<T>(T,)
实现:
imp GenVal<i32>针对GenVal<i32>类型
imp <T> GenVal<T>是泛化实现
函数
调用泛型函数有时需要显式地指明类型参量。这可能是因为调用了返回类型是泛型的函数,或者编译器没有足够的信息来推断类型参数。
调用函数时,使用显式指定的类型参数会像是这样:fun::<A, B, ...>()。
struct A; // 具体类型 `A`。
struct S(A); // 具体类型 `S`。
struct SGen<T>(T); // 泛型类型 `SGen`。
// 下面全部函数都得到了变量的所有权,并立即使之离开作用域,将变量释放。
// 定义一个函数 `reg_fn`,接受一个 `S` 类型的参数 `_s`。
// 因为没有 `<T>` 这样的泛型类型参数,所以这不是泛型函数。
fn reg_fn(_s: S) {}
// 定义一个函数 `gen_spec_t`,接受一个 `SGen<A>` 类型的参数 `_s`。
// `SGen<>` 显式地接受了类型参数 `A`,且在 `gen_spec_t` 中,`A` 没有被用作
// 泛型类型参数,所以函数不是泛型的。
fn gen_spec_t(_s: SGen<A>) {}
// 定义一个函数 `gen_spec_i32`,接受一个 `SGen<i32>` 类型的参数 `_s`。
// `SGen<>` 显式地接受了类型参量 `i32`,而 `i32` 是一个具体类型。
// 由于 `i32` 不是一个泛型类型,所以这个函数也不是泛型的。
fn gen_spec_i32(_s: SGen<i32>) {}
// 定义一个函数 `generic`,接受一个 `SGen<T>` 类型的参数 `_s`。
// 因为 `SGen<T>` 之前有 `<T>`,所以这个函数是关于 `T` 的泛型函数。
fn generic<T>(_s: SGen<T>) {}
fn main() {
// 使用非泛型函数
reg_fn(S(A)); // 具体类型。
gen_spec_t(SGen(A)); // 隐式地指定类型参数 `A`。
gen_spec_i32(SGen(6)); // 隐式地指定类型参数 `i32`。
// 为 `generic()` 显式地指定类型参数 `char`。
generic::<char>(SGen('a'));
// 为 `generic()` 隐式地指定类型参数 `char`。
generic(SGen('c'));
}
trait
trait 也可以是泛型的。我们在这里定义了一个 trait,它把 Drop trait 作为泛型方法实现了,可以 drop(丢弃)调用者本身和一个输入参数。
// 不可复制的类型。
struct Empty;
struct Null;
// `T` 的泛型 trait。
trait DoubleDrop<T> {
// 定义一个调用者的方法,接受一个额外的参数 `T`,但不对它做任何事。
fn double_drop(self, _: T);
}
// 对泛型的调用者类型 `U` 和任何泛型类型 `T` 实现 `DoubleDrop<T>` 。
impl<T, U> DoubleDrop<T> for U {
// 此方法获得两个传入参数的所有权,并释放它们。
fn double_drop(self, _: T) {}
}
fn main() {
let empty = Empty;
let null = Null;
// 释放 `empty` 和 `null`。
empty.double_drop(null);
//empty;
//null;
// ^ 试一试:去掉这两行的注释。
}
约束
在使用泛型时,类型参数常常必须使用 trait 作为约束(bound)来明确规定类型应实现哪些功能。例如下面的例子用到了 Display trait 来打印,所以它用 Display 来约束 T,也就是说 T 必须实现 Display。
// 定义一个函数 `printer`,接受一个类型为泛型 `T` 的参数,
// 其中 `T` 必须实现 `Display` trait。
fn printer<T: Display>(t: T) {
println!("{}", t);
}
约束把泛型类型限制为符合约束的类型
struct S<T: Display>(T);
// 报错!`Vec<T>` 未实现 `Display`。此次泛型具体化失败。
let s = S(vec![1]);
多重约束
多重约束(multiple bounds)可以用 + 连接。和平常一样,类型之间使用 , 隔开。
use std::fmt::{Debug, Display};
fn compare_prints<T: Debug + Display>(t: &T) {
println!("Debug: `{:?}`", t);
println!("Display: `{}`", t);
}
fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {
println!("t: `{:?}`", t);
println!("u: `{:?}`", u);
}
fn main() {
let string = "words";
let array = [1, 2, 3];
let vec = vec![1, 2, 3];
compare_prints(&string);
//compare_prints(&array);
// 试一试 ^ 将此行注释去掉。
compare_types(&array, &vec);
}
where从句
约束也可以使用 where 分句来表达,它放在 { 的前面,而不需写在类型第一次出现之前。另外 where 从句可以用于任意类型的限定,而不局限于类型参数本身。
where 在下面一些情况下很有用:
- 当分别指定泛型的类型和约束会更清晰时:
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}
// 使用 `where` 从句来表达约束
impl <A, D> MyTrait<A, D> for YourType where
A: TraitB + TraitC,
D: TraitE + TraitF {}
- 当使用 where 从句比正常语法更有表现力时。本例中的 impl 如果不用 where 从句,就无法直接表达。
use std::fmt::Debug;
trait PrintInOption {
fn print_in_option(self);
}
// 这里需要一个 `where` 从句,否则就要表达成 `T: Debug`(这样意思就变了),
// 或者改用另一种间接的方法。
impl<T> PrintInOption for T where
Option<T>: Debug {
// 我们要将 `Option<T>: Debug` 作为约束,因为那是要打印的内容。
// 否则我们会给出错误的约束。
fn print_in_option(self) {
println!("{:?}", Some(self));
}
}
fn main() {
let vec = vec![1, 2, 3];
vec.print_in_option();
}
关联项
简单来说 A,B具体类型就能确定C的类型,这种情况就可以使用关联类型。
例如:
struct Container(i32, i32);
// 这个 trait 检查给定的 2 个项是否储存于容器中
// 并且能够获得容器的第一个或最后一个值。
trait Contains<A, B> {
fn contains(&self, _: &A, _: &B) -> bool; // 显式地要求 `A` 和 `B`
fn first(&self) -> i32; // 未显式地要求 `A` 或 `B`
fn last(&self) -> i32; // 未显式地要求 `A` 或 `B`
}
impl Contains<i32, i32> for Container {
// 如果存储的数字和给定的相等则为真。
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// 得到第一个数字。
fn first(&self) -> i32 { self.0 }
// 得到最后一个数字。
fn last(&self) -> i32 { self.1 }
}
// 容器 `C` 就包含了 `A` 和 `B` 类型。鉴于此,必须指出 `A` 和 `B` 显得很麻烦。
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> {
container.last() - container.first()
}
fn main() {
let number_1 = 3;
let number_2 = 10;
let container = Container(number_1, number_2);
println!("Does container contain {} and {}: {}",
&number_1, &number_2,
container.contains(&number_1, &number_2));
println!("First number: {}", container.first());
println!("Last number: {}", container.last());
println!("The difference is: {}", difference(&container));
}
上面例子可以改造为:
struct Container(i32, i32);
// 这个 trait 检查给定的 2 个项是否储存于容器中
// 并且能够获得容器的第一个或最后一个值。
trait Contains {
// 在这里定义可以被方法使用的泛型类型。
type A;
type B;
fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
impl Contains for Container {
// 指出 `A` 和 `B` 是什么类型。如果 `input`(输入)类型
// 为 `Container(i32, i32)`,那么 `output`(输出)类型
// 会被确定为 `i32` 和 `i32`。
type A = i32;
type B = i32;
// `&Self::A` 和 `&Self::B` 在这里也是合法的类型。
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
// 得到第一个数字。
fn first(&self) -> i32 { self.0 }
// 得到最后一个数字。
fn last(&self) -> i32 { self.1 }
}
fn difference<C: Contains>(container: &C) -> i32 {
container.last() - container.first()
}
fn main() {
let number_1 = 3;
let number_2 = 10;
let container = Container(number_1, number_2);
println!("Does container contain {} and {}: {}",
&number_1, &number_2,
container.contains(&number_1, &number_2));
println!("First number: {}", container.first());
println!("Last number: {}", container.last());
println!("The difference is: {}", difference(&container));
}
虚类型参数
虚类型(phantom type)参数是一种在运行时不出现,而在(且仅在)编译时进行静态检查的类型参数。
可以用额外的泛型类型参数指定数据类型,该类型可以充当标记,也可以供编译时类型检查使用。这些额外的参数没有存储值,也没有运行时行为。
在下面例子中,我们使用 std::marker::PhantomData 作为虚类型参数的类型,创建包含不同数据类型的元组。
use std::marker::PhantomData;
// 这个虚元组结构体对 `A` 是泛型的,并且带有隐藏参数 `B`。
#[derive(PartialEq)] // 允许这种类型进行相等测试(equality test)。
struct PhantomTuple<A, B>(A,PhantomData<B>);
// 这个虚类型结构体对 `A` 是泛型的,并且带有隐藏参数 `B`。
#[derive(PartialEq)] // 允许这种类型进行相等测试。
struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> }
// 注意:对于泛型 `A` 会分配存储空间,但 `B` 不会。
// 因此,`B` 不能参与运算。
fn main() {
// 这里的 `f32` 和 `f64` 是隐藏参数。
// 被指定为 `<char, f32>` 的 `PhantomTuple` 类型。
let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData);
// 被指定为 `<char, f64>` `PhantomTuple` 类型。
let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData);
// 被指定为 `<char, f32>` 的类型。
let _struct1: PhantomStruct<char, f32> = PhantomStruct {
first: 'Q',
phantom: PhantomData,
};
// 被指定为 `<char, f64>` 的类型。
let _struct2: PhantomStruct<char, f64> = PhantomStruct {
first: 'Q',
phantom: PhantomData,
};
// 编译期错误!类型不匹配,所以这些值不能够比较:
//println!("_tuple1 == _tuple2 yields: {}",
// _tuple1 == _tuple2);
// 编译期错误!类型不匹配,所以这些值不能够比较:
//println!("_struct1 == _struct2 yields: {}",
// _struct1 == _struct2);
}