Ok, I have a very, very early proof of concept here.
It consists in:
- a module, which defines units of measures and their basic operations;
- example definitions of the four SI base units;
- a type-checker, implemented as a driver for rustc.
It’s missing lots of syntactic sugar.
The type-checker correctly accepts:
// Trivial case
fn get_speed(distance: Measure<f64, Meter>, duration: Measure<f64, Second>) -> Measure<f64, PMul<Meter, PInv<Second>>> {
return (distance / duration).unify();
}
Normally, unify()
should be part of the syntactic sugar. There may be a way to hide it without syntactic sugar, but I haven’t found yet.
The following is also accepted:
// Less trivial case, as we need to convert 1/ Second * Meter => Meter * Second
fn get_speed_2(distance: Measure<f64, Meter>, duration: Measure<f64, Second>) -> Measure<f64, PMul<Meter, PInv<Second>>> {
return ((PDimensionless::new(1.) / duration) * distance ).unify();
}
// Not terribly hard, but it shows that we can still use `fold` & co.
fn get_average<U: Unit>(number: usize) -> Measure<f64, U> {
let total = (1..number)
.map(|x| x as f64)
.map(U::new)
.fold(U::new(0.), std::ops::Add::add);
total / (number as f64)
}
/// We can even simplify type variables.
trait Distance: Unit {}
trait Duration: Unit {}
fn get_speed_generic<A: Distance, B: Duration>(distance: Measure<f64, A>, duration: Measure<f64, B>) -> Measure<f64, PMul<A, PInv<B>>> {
return (distance / duration).unify();
}
And it correctly rejects
/// No, Kilometer is not the same as Meter. Ugly error message for the time being.
struct Kilometer;
impl Unit for Kilometer {}
fn get_speed_bad(distance: Measure<f64, Kilometer>, duration: Measure<f64, Second>) -> Measure<f64, PMul<Meter, PInv<Second>>> {
return ((PDimensionless::new(1.) / duration) * distance ).unify();
}
Still lots to do, but so far, so good