On Tuesday, 28 July 2020 at 04:40:33 UTC, Cecil Ward wrote:
I found an earlier post somewhere about work someone has done
on physical units such as kg, volts and so forth.
It would be very good to catch bugs such as
volts_t v = input_current;
[...]
This is easily done and uses enums: (officially defined in the
specification, not even a hack!)
enum DisplayUnit : int { init = 0 }
enum Width : DisplayUnit { init = DisplayUnit.init }
enum Height : DisplayUnit { init = DisplayUnit.init }
void foo()
{
Width width = cast(Width)16;
Height height = cast(Height)9;
//width = height; // illegal implicit conversion
//width = 4; // illegal implicit conversion
width = cast(Width)height; // legal, force conversion
DisplayUnit val = width; // allowed implicit conversion,
Width "inherits" DisplayUnit
int num = width; // also allowed, Width "inherits"
DisplayUnit which "inherits" int
writeln(num); // 9
writeln(width); // cast(Width)9
}
You can then also make helper functions for prettier syntax, see
https://run.dlang.io/is/X4BA32
But that isn’t nearly enough. With strong typing where we can
create arbitrary subtypes that are chosen to be incompatible
because they are semantically incompatible in assignment,
equality, addition and various other operations, we can catch a
lot more bugs.
Sadly width += height (and probably all operators other than
assignment) are still allowed.
However for return codes this makes great sense: it implicitly
converts down to integers if the user wishes to lose type
information and otherwise prevents assigning or calling wrong
functions like some example `enum Win32Error : int` could not be
used to call a `validate(PosixError)` function
This is also great for opaque handles like for making file
descriptors type-safe: `enum File : int`, `enum Socket : int`
which you can't accidentally pass to type-safe APIs but can still
easily use in C APIs using ints or raw file descriptor access.
All your wrapper functions simply cast the internal C API return
values to these type-safe enum types based on context they know.
For other values representing something it doesn't work as great
as you need to have a helper function to construct them or always
cast them as well as to!string (and writeln) don't print the raw
value but rather prepend a "cast(T)" in the string, but it still
works and is possible.