Rust-03

97 阅读7分钟

泛化类型和函数功能,以扩大其适用范围,大大减少代码重复。采用泛型意味着仔细地指定泛型类型具体化时,什么样的具体类型是合法的。泛型最简单和常用的用法是用于类型参数。

结构体

结构体泛型,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);
}