The subtyping feature I love the most. You see the benefit when you need to call a function which is taking many numeric arguments, all of them is a subtype so you can't give the arguments in the wrong order (latitude and longitude for example).
The subtyping feature I love the most. You see the benefit when you need to call a function which is taking many numeric arguments, all of them is a subtype so you can't give the arguments in the wrong order (latitude and longitude for example).
I only have very little experience with Ada, but being able to define a type whose values are integers in the range, say, 1 .. 7, to represent days of a week was an eye opener.
But even without range constraints, wrapper types (called newtypes in Haskell and Rust) are great. Let's say you have a function that accepts a temperature. A temperature is a float, but you want to prevent the user from mixing up celsius and fahrenheit.
This is what newtype looks like in Rust:
struct Celsius(pub f64);
struct Fahrenheit(pub f64);
fn print_celsius(temperature: Celsius) {
println!("Temperature is {}", temperature.0);
}
It will fail to compile if you accidentally pass a Fahrenheit value to `print_celsius`, but at compile time all values are optimized down to plain floats, without any runtime cost.More programming languages should adopt this concept :)
Not that you can use the language-provided == on floats anyway, due to precision issues you should always check if the difference is below a specified limit...
Hehe, I've been there. ;-) Fortunately, that code dealt with physical coordinates, so if two points were less than 100µm apart, they were equal for our purposes.
#include <stdio.h>
typedef struct { double v; } Celsius;
typedef struct { double v; } Fahrenheit;
void
print_celsius(Celsius temperature) {
printf("Temperature is %f", temperature.v);
}
void
compile_error(Fahrenheit temperature)
{
print_celsius(temperature);
}
I suspect LLVM will clean this up to plain floats as well.That said, yes, modern ABIs for modern machines usually pass single-element structures of primitive types in registers of the appropriate type. x86 (32-bit) is not modern in this sense: depending on the exact ABI used, these structures might well be passed on the stack or in an integer register.
The only advantage of Rust in this case is that its tuple syntax can be a bit nicer than C structs and the language supports overloading so that you can reimplement the comparison operators for instance (or do more complicated things, for instance if you have a "decibel" type that must have a special addition implementation)
I don't get why no modern language adopted them...
[someObject doSomethingWithLatitude:55.0 longitude:0.0];