This is more than 50 years old Pascal. As far as I know the only mainstream language that supports this in its type system (and compile-time checked) today is Ada[1]. Blows my mind. I never got over the loss when I switched to C.
[1] I think you could convince Rust to do something pretty close but a bit uglier syntax-wise.
This is what is known as new types or opaque types in other languages, right? The runtime representation is that of the "wrapped" type, but they are distinguished at compile-time.
However, this is definitely a hack, and I sort-of feel the same about the Zig solution from the article. Would be nice if languages had 'cleaner' support for this?
// Foo and Bar cannot be assigned to each other
type Foo = Foo of int
type Bar = Bar of int
// Foo and Bar can be assigned to each other
type Foo = int
type Bar = int
The first variant uses single-case unions. It's a bit unfortunate that they also default to classes over structs unless you annotate them with [<Struct>], partially fixed by escape analysis though.
The enum trick is the tersest and will also serialize correctly in most cases, but I have never seen anyone use it like that before.
When using implicit typing, definitely `var foo = (int Foo)12`. Less sure what the explicit variant should look like, since parsing may be trickier, but `(int Foo) foo = 12` might work?
In a past job I implemented these with heavy use of C++ operator overrides. Not the prettiest option, but it stopped the six id/index types I was using from accidentally getting misused. Something like - ObjectId, Resource Id, User Id, Group Index, some other things. This was for the ingame handling for https://store.sansar.com/ - it's a pity the project kind of died, we had some neat ideas for payment for providence. The idea thrown around was that "if I make some wheels and put them on the marketplace, you can make a car, sell it, and I'd get a cut of every car sale". This was of course, ignoring the "analog hole" of someone figuring out how to export/reimport your asset.
You can accomplish the same thing in C with some macros: https://stackoverflow.com/a/376478/189247 But honestly, it is not worth the bother. You just end up with lots of (my_index_type_is_different_from_yours_t) casts littered in your code.
I am not an active Zig guy, so... YYMV, but... To me, the examples are actually not self explanatory. What does the _ do? How are these constants used? Why is it something special? C had enums since forever, what is the novelty here?
C has a rather weak type system: using one enum type in a function that takes a different enum isn't guaranteed to be an error, instead they promote to an integer type and silently convert (though you can set up most compilers to make this an error, e.g. `-Wenum-conversion` and `-Werror` for GCC & Clang).
The basic idea is the "newtype" pattern: different types may have exactly the same valid set of values, but not be interchangeable.
E.g. kilograms and US customary pounds, or meters and miles. If you've got a function `f(distance: double)` where `distance` needs to be in kilometers but your program also handles miles, it's nice to be able to define a type for kilometers that's different from the type for miles so the function won't compile if passed in miles incorrectly. So you get `f(distance: kilometers)`. That also stops someone passing in pounds to `f`, or any other such nonsense.
[1] I think you could convince Rust to do something pretty close but a bit uglier syntax-wise.