新类型

如果在某些情况下,我们希望一个类型的行为类似于另一个类型,或者在编译时强制执行一些行为,而仅仅使用类型别名是不够的,怎么办?

例如,如果我们出于安全考虑(如密码),想为String创建一个自定义的Display实现。

对于这种情况,我们可以使用Newtype模式来提供类型安全封装

描述

使用单个字段的元组结构体为一个类型做不透明包装。 这将创建一个新的类型,而不是一个类型的别名(type项)。

例子

// Some type, not necessarily in the same module or even crate.
struct Foo {
    //..
}

impl Foo {
    // These functions are not present on Bar.
    //..
}

// The newtype.
pub struct Bar(Foo);

impl Bar {
    // Constructor.
    pub fn new(
        //..
    ) -> Self {

        //..

    }

    //..
}

fn main() {
    let b = Bar::new(...);

    // Foo and Bar are type incompatible, the following do not type check.
    // let f: Foo = b;
    // let b: Bar = Foo { ... };
}

动机

新类型的主要动机是抽象化。它允许你在类型之间共享实现细节,同时精确控制接口。 通过使用新类型而不是将实现类型作为API的一部分公开,它允许你向后兼容地改变实现。

新类型可以用来区分单位,例如,包装f64以获得可区分的MilesKms

优势

被包装的类型和包装后的类型不是类型兼容的(相对于使用type),所以新类型的用户永远不会“混淆“包装前后的类型。

新类型是一个零成本的抽象——没有运行时的开销。

隐私系统确保用户无法访问被包装的类型(如果字段是私有的,默认情况下是私有的)。

劣势

新类型的缺点(尤其是与类型别名相比)是没有特殊的语言支持。这意味着可能会有许多模板代码。 你需要为你想在包装类型上公开的每个方法提供一个”通过“方法,并为你想在包装类型上实现的每个trait提供一个实现。

讨论

新类型在Rust代码中非常常见。抽象或代表单位是最常见的用途,但它们也可以用于其他原因:

  • 限制功能(减少暴露的函数或实现的trait),
  • 使一个具有复制语义的类型具有移动语义,
  • 通过提供一个更具体的类型,从而隐藏内部类型来实现抽象, 例如,
pub struct Foo(Bar<T1, T2>);

这里,Bar可能是一些公共的、通用的类型,T1T2是一些内部类型。 我们模块的用户不应该知道我们通过使用Bar来实现Foo,但我们在这里真正隐藏的是T1T2类型,以及它们如何与Bar一起使用。

参见

Latest commit 11a0a13 Dec 14 2021