Search code examples
rusttyping

Can I define my own "strong" type alias in Rust?


tl;dr in Rust, is there a "strong" type alias (or typing mechanism) such that the rustc compiler will reject (emit an error) for mix-ups that may be the same underlying type?

Problem

Currently, type aliases of the same underlying type may be defined

type WidgetCounter = usize;
type FoobarTally = usize;

However, the compiler will not reject (emit an error or a warning) if I mistakenly mix up instances of the two type aliases.

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);
}

(Rust Playground)

Possible Solutions?

I'm hoping for something like an additional keyword strong

strong type WidgetCounter = usize;
strong type FoobarTally = usize;

such that the previous code, when compiled, would cause a compiler error:

error[E4444]: mismatched strong alias type WidgetCounter,
              expected a FoobarTally

Or maybe there is a clever trick with structs that would achieve this?

Or a cargo module that defines a macro to accomplish this?



I know I could "hack" this by type aliasing different number types, i.e. i32, then u32, then i64, etc. But that's an ugly hack for many reasons.


Is there a way to have the compiler help me avoid these custom type alias mixups?


Solution

  • Rust has a nice trick called the New Type Idiom just for this. By wrapping a single item in a tuple struct, you can create a "strong" or "distinct" type wrapper.

    This idiom is also mentioned briefly in the tuple struct section of the Rust docs.

    The "New Type Idiom" link has a great example. Here is one similar to the types you are looking for:

    // 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);
    }