#brand #tags #safety #branded-types #type-tag

bty

简化Rust中品牌类型的定义和使用

3个不稳定版本

0.2.0 2024年4月13日
0.1.0 2024年4月1日
0.1.0-pre.12023年4月29日

#618 in Rust模式

42 每月下载量

MIT 协议

14KB
205

bty

github crates.io docs.rs build status

简化Rust中品牌类型的定义和使用。

此软件包提供了Brand类型和brand!宏,可以用于在Rust中声明和无缝使用品牌类型。

[dependencies]
bty = "0.1"

支持rustc 1.60+

示例

可以使用brand!宏来声明品牌类型,这些类型将根据类型别名的名称进行区分。

bty::brand!(
    type UserId = i32;
);

UserId的实例可以使用反序列化实现之一构建,例如serdesqlx。虽然不推荐,但可以使用关联函数unchecked_from_inner手动实例化。

请参阅Matt Pocock在Twitter上发布的这个帖子,以获得更具体和直观的示例。尽管它展示了TypeScript的示例,但原理是相同的。

理由

虽然值属于同一类型,但属于不同领域的情况并不少见。例如,一个Web应用程序可以使用i32类型来表示用户ID和订单ID。

虽然这看起来合理,因为不同的域类型具有相同的类型,但很容易将一个用户ID传递给期望一个订单ID的函数。

由于Rust的类型系统是命名的,因此可以通过为每个ID引入不同的类型来避免这个问题。例如,可以这样做:

pub struct UserId(i32);

pub struct OrderId(i32);

现在编译器可以静态地确保用户ID永远不会被错误地传递给订单ID。太棒了!

尽管这种方法适用于大多数情况,但随着自定义ID类型数量的增加,它会变得难以管理,因为为了可用性的原因,类型定义本身很少足够。例如,要CloneDebug自定义ID,必须为所有自定义类型实现这些特性。

#[derive(Clone, Debug)]
pub struct UserId(i32);

#[derive(Clone, Debug)]
pub struct OrderId(i32);

当ID类型的用途增加时,问题变得更加严重。例如,关于serde序列化和反序列化呢?

bty通过不使用品牌类型的独立类型来解决此问题。相反,使用单个Brand类型。定义为Brand<Tag, Inner>,它是一个泛型类型,具有一个Tag类型,该类型区分不同“品牌”(即领域)的值和由Inner表示的底层类型。

对于大多数Rust常用特质,如果Inner实现了它,那么Brand也会实现。这意味着如果Inner实现了CloneDebug,那么Brand<_, Inner>也会实现它们。

按照前面的例子,可以使用bty并拥有

bty::brand!(
    pub type UserId = i32;
    pub type OrderId = i32;
);

i32类型并没有什么特殊之处。就像手动定义的结构体一样,任何类型都可以用来构建一个品牌类型。

许可证

MIT许可证。

依赖项

~0.1–1.8MB
~36K SLoC