Any RFC for Units of Measure?

I agree. One approach can look like this.

Std code:

// assuming we'll get const trait fns
// here const guarantees that trait have only const methods
trait UnitSystem: const Div + const Mul + const BitXor { }

struct UnitValue<Value, U: UnitSystem, const UNIT: U> {
    value: Value,
   // not sure if we need PhantomData field here
   phantom: PhantomData<..>,
}

impl<V1, V2, U, const U1: U, const U2: U> Mul<RHS=UnitValue<V1, U, U1>>
    for UnitValue<V2, U, U2>
    where U: UnitSystem, V2: const Mul<V1> 
{
    type Output = UnitValue<V2::Output, U, U1*U2>;
    // ideally we should be able to define `const fn mul` if `Mul<V1>` is const
    // and `fn mul` if `Mul<V1>` is not const,
    // it's probably should be possible with specialization
    // but to start things we can do this impl only for constant `Mul`s
    const fn mul(self, rhs: Self::RHS) -> Self::Output {
        UnitValue { value: self.value*rhs.value, phantom: Default::default() }
    }
}

// generic impls for Div, Add, etc., some inhrent methods for f32, f64, etc.

We also can add helper methods to UnitSystem trait for conversion between unit systems, but I am not completely sure how they should look.

Unit system crate:

struct SI {
    meter: i8,
    second: i8,
    kelvin: i8,
    // ..
}

impl Mul for SI {
    type Output = SI;
    
    const fn mul(mut self, rhs: SI) -> Self {
        self.meter += rhs.meter;
        self.second += rhs.second;
        self.kelvin += rhs.kelvin;
        // ..
        self
    }
}

impl Div for SI {
    type Output = SI;
    
    const fn div(mut self, rhs: SI) -> Self {
        self.meter -= rhs.meter;
        self.second -= rhs.second;
        self.kelvin -= rhs.kelvin;
        // ..
        self
    }
}

// we hijack `^` operator here, but it will make code significantly nicer
impl BitXor<RHS=i8> for SI {
    type Output = SI;
    
    const fn bit_xor(mut self, rhs: i8) -> Self {
        self.meter *= rhs;
        self.second *= rhs;
        self.kelvin *= rhs;
        // ..
        self
    }
}

impl UnitSystem for SI {}

// f32  module
type SIVal<const UNIT: SI> = UnitValue<f32, SI, UNIT>;
const Meter = SI { meter: 1, second: 0, kelvin: 0, .. };
const Second = SI { meter: 0, second: 1, kelvin: 0, .. };
const Hertz = Second^-1;

User code:

fn foo<const U1: SI, const U2: SI>(
    a: SIVal<U1>, b: SIValF32<U1>, c: SIVal<U2>
) -> SIVal<U1/U2> {
    (a + b)/c
}

fn bar(length: SIVal<Meter>, time: SIVal<Second>) -> SIVal<Meter/Second^2> {
    2.0*length/(time*time)
}

The drawbacks of this system are:

  • Reliance on some features without implementation plan (const trait fns, const trait bounds). It can be circumvented a bit, if we'll move div and mul methods to UnitSystem trait, but it will make generic code a bit more verbose, as you will not be able to use * and /.
  • Unit system can not be extended by third-party code. Though arguably in practice it shouldn't be a big problem.
  • You will not be able to create derivative unit systems easily, e.g. by replacing meter with millimeter.
  • Generic functions will be still quite unwieldy.