Rust 和领域驱动设计使某些错误无法编写

Rust and Domain-Driven Design to make certain errors impossible to write

我正在努力解决如何在 Rust 中实现一些在 TypeScript 中非常简单的东西。本质上是一种 sum 类型的产品类型,在编译时执行业务规则

假设我有一个结构 Location,它包含两个属性,statecity,其中 state: StateEnumcity: String

一般来说,citystate分别可以容纳任何StateEnum/String。到目前为止,还不错:

struct Location {
  state: StateEnum,
  city: String,
}

但是,有些业务规则使得 state 的某些值限制了某些 city 的有效性。显然你不能有一个有效的 Location { state: StateEnum::TX, city: "New York City }.

在 OOP 中,您可以将 city 设为私有并让 setCity 检查并抛出异常,如果该城市对于该州执行业务规则不合法。 运行时间异常。

在 FP 中,您可以编写自己的类型,因此类似于

type NYLocation = {
  state: NY
  city: 'Albany' | 'NYC' ...
}

type TexasLocation = { ... }

type Location = NYLocation | TexasLocation | ...

和你的 IDE 会警告你不能将 TexasLocation.city 设置为 newCity: string 除非先在代码中使用类型保护来缩小 string 的可能值至 'Houston' | 'Austin' | ....

有没有办法(我的意思是,显然必须有办法;是否有 惯用的 方法在 Rust 中而不是 运行-像 OOP 一样的时间?

在某种程度上,是的。您可以使用枚举静态地强制执行离散值域,就像您已经对状态所做的那样。你只需要将这个概念扩展到城市。

您可以为每个州的城市创建一个枚举,然后让 Location 成为一个枚举。

enum NewYorkCities {
    Albany,
    NewYorkCity,
    // ...
}

enum WashingtonCities {
    Seattle,
    Olympia,
    // ...
}

enum Location {
    NewYork(NewYorkCities),
    Washington(WashingtonCities),
    // ...
}

这使您能够证明不在给定域中的值不能存在于程序中。

最后,您只需要一种将每个枚举与字符串相互转换的方法。这些类型的函数可以由 strum crate 中的宏生成。

显然这需要重新编译以更改可接受的域,但无论如何您都会遇到与 TypeScript 代码相同的问题,所以我认为这是一个可以接受的缺点。