「Generics 解密」1-1

676 阅读4分钟

「这是我参与11月更文挑战的第 3 天,活动详情查看:2021最后一次更文挑战


介绍

Rustaceans 欣赏泛型有三个原因:

  • 泛型是编译时的抽象概念。可以取代dyn关键字代替泛型,但是它有运行时的开销
  • 泛型使代码更干净、更可重复使用。由于有了 trait 约束,代码可以描述当这些界线被满足时它能做什么。你的代码变得如你所想的那样灵活。
  • 泛型将帮助你理解 生命周期 (这也是Rust中很难以理解的一个概念)。本书用命令式代码(即函数体中的代码)来解释生命周期。但是泛型生命周期有声明性的上下文。这使得它们在类型、特征和实现中使用时具有明确的意义。

让我们来学习关于泛型的所有知识。我们将通过循序渐进的学习来做到这一点,以便在我们回去学习之前消化掉其中的每一个部分。

目标

通过几个简单的例子,可以让所有通过本文学习的人做好准备,自信地使用泛型。泛型使我们能够有选择地进行编码。一旦理解了这些,在适当的地方插入泛型的概念就会自然而然地出现。

从一个简单的例子开始,我们将通过引入类型的灵活性来完成泛型(或参数化)类型应该做的事情。然后,我们可以慢慢地增加复杂性,并学习为什么泛型会渗透到类型系统的各个方面,为什么它们需要可选择的限制,以及我们如何使用这个强大的工具,而不把我们的代码变成神秘的东西。到最后,我们将完全控制Rust的类型系统。

本指南将通过大量的代码实例引导你完成一个小库的编写过程。每个部分都可以在30分钟内完成。如果你跟着做,每一部分之后的代码都会被编译。第一部分将涵盖 类型、函数和闭包 的基础知识。第二部分将深入探讨 trait以及它的实现。最后,第三部分将添加 常量泛型和泛型参数生命周期。由此产生的库将是简单但有趣和易于理解的。

基础

先来学习一下 Rust 类型系统的基础部分。

类型

struct Point {    
    id: u64
}

上面的代码是我们熟悉的,u64是一个硬编码的(非泛型)类型。它没有提供任何灵活性,但却足够而有选择性。

struct Point<Id> {    
    id: Id
}

Point 可以使用任何其他类型作为标识。Id甚至可以是 PointVec<Point> ,不过这看起来很荒唐:类似 Point<Vec<Point<()>

但是这种灵活性有点过了,这就是where子句在以后我们使用实现时的作用。现在,我们可以对用户保持 Point<Id> 类型的私有性,同时我们用一个有意义的Id暴露公共类型。

我们可以在这个类型定义中使用 trait约束 。但你很少会看到这样做。其原因将在第二部分中详细介绍,在那个环节我们将编写自己的trait以及它的实现。

pub type Point = inner::Point<u32>;
pub type BigPoint = inner::Point<u128>;

mod inner {
    struct Point<Id> {
        id: Id
    }
}

如果 Id value 可以随机生成,那么像u128这样较大的类型在大量生成BigPoint实例时,可以降低碰撞的几率。但是对于较少的Points,u32类型将只使用四个字节,而不是每个u128使用的十六个字节。这是一个简化的例子,说明泛型在定义类型时是如何有用的。

我们现在已经看到了三个层次的灵活性:

  • 硬编码的(非泛型)类型。
  • 一个高度灵活的开放式泛型。
  • 一个私有空间使用的通用类型可以作为别名插在公共接口上。