我可以在 Rust 中定义自己的“强”类型别名吗?

Jam*_*979 17 typing rust

tl;dr在 Rust 中,是否存在“强”类型别名(或类型机制),使得rustc编译器会拒绝(发出错误)可能是相同底层类型的混淆?

问题

目前,可以定义相同底层类型的类型别名

type WidgetCounter = usize;
type FoobarTally = usize;
Run Code Online (Sandbox Code Playgroud)

但是,如果我错误地混淆了两种类型别名的实例,编译器不会拒绝(发出错误或警告)。

fn tally_the_foos(tally: FoobarTally) -> FoobarTally {
    // ...
    tally
}

fn main() {
    let wc: WidgetCounter = 33;
    let ft: FoobarTally = 1;

    // whoops, passed the wrong variable!
    let tally_total = tally_the_foos(wc);
}
Run Code Online (Sandbox Code Playgroud)

铁锈游乐场

可能的解决方案?

我希望有一个额外的关键字之类的东西strong

strong type WidgetCounter = usize;
strong type FoobarTally = usize;
Run Code Online (Sandbox Code Playgroud)

这样前面的代码在编译时会导致编译器错误:

error[E4444]: mismatched strong alias type WidgetCounter,
              expected a FoobarTally
Run Code Online (Sandbox Code Playgroud)

或者也许 s 有一个巧妙的技巧struct可以实现这一点?

或者定义宏来完成此任务的货物模块?



我知道我可以通过类型别名不同的数字类型来“破解”这个,即i32、 then u32、 theni64等。但由于很多原因,这是一个丑陋的黑客。


有没有办法让编译器帮助我避免这些自定义类型别名混淆?

小智 15

Rust 有一个很好的技巧,叫做New Type Idiom就是为了这个目的。通过将单个项目包装在元组结构中,您可以创建“强”或“不同”类型的包装器。

Rust 文档的元组结构部分也简要提到了这个习惯用法。

“新类型习语”链接有一个很好的例子。这是与您正在寻找的类型类似的一种:

// Defines two distinct types. Counter and Tally are incompatible with
// each other, even though they contain the same item type.
struct Counter(usize);
struct Tally(usize);

// You can destructure the parameter here to easily get the contained value.
fn print_tally(Tally(value): &Tally) {
  println!("Tally is {}", value);
}

fn return_tally(tally: Tally) -> Tally {
  tally
}

fn print_value(value: usize) {
  println!("Value is {}", value);
}

fn main() {
  let count: Counter = Counter(12);
  let mut tally: Tally = Tally(10);

  print_tally(&tally);
  tally = return_tally(tally);

  // This is a compile time error.
  // Counter is not compatible with type Tally.
  // print_tally(&count);

  // The contained value can be obtained through destructuring
  // or by potision.
  let Tally(tally_value ) = tally;
  let tally_value_from_position: usize = tally.0;

  print_value(tally_value);
  print_value(tally_value_from_position);
}
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,使用此习惯用法更改现有代码需要添加/删除“.0”。如果能够自动推断出单字段元组结构的“.0”(“非元组结构强制转换”?),以便使“类型”的人体工程学与元组的类型安全/严格性相结合,那就太好了结构。 (7认同)
  • 作为一个额外的好处,您可以使用此模式在外部类型上实现外部特征。 (4认同)
  • 不幸的是,您需要进行这种解构,因此它与“强类型”不太一样。我相信Golang有这样的“强类型”。在 Rust 中也能拥有它就好了。 (3认同)
  • @BallpointBen 我不同意。我明白你的观点,即更改现有代码会更容易,但没有“.0”会破坏这个习惯用法的目的:“.0”是包装器与其底层类型之间的显式转换。隐式强制转换可能会创建看起来不明确的代码,并且会导致本问题试图防止的相同类型的错误被忽视。 (2认同)