Can we use type system to do type calculation?

I've been thinking about and searching for a crate that helps me handles physics units. There are some crates but they don't do what I want to do.

I want to see if I can implement something that performs unit calculation at compile time using Rust type system, since it's Turing complete according to some posts. I think it will be fun to write something like this. Specifically, I can define types based on International System of Units, say meter, second and kg. Then I can define derived units, newton (kg⋅m⋅s^(−2)) for example. An ideal sample of code is like

pub struct Meter(f32);
pub struct Kilogram(f32);
pub struct Second(f32);

// somehow to define newton as kg⋅m⋅s^(−2)
pub struct Mul<A, B>;
pub struct Order<T, const N: isize>;
type Newton = Mul<Kilogram, Mul<Meter, Order<Second, -2>>>; // #1

// in main
let meter = Meter(1.);
let kg = Kilogram(1.);
let second = Second(1.0);
let newton : Newton = meter * kg / (second * second); // need to handle Mul and Div traits

but this type aliasing at #1 cannot handle the transtivity, because we can also define type Newton = Mul<Meter, Mul<Kilogram, Order<Second, -2>>>;. And it seems Rust type system cannot allow some sort of transtivity now.

Another problem is about const generics because if we multiply units like 1m * 1m, we will get 1m^2 and if we define something like struct Order<T, const N: isize>, we will need to somehow raise the number of N, like this:

pub fn same_unit_multiply<T, const N: isize, const M: isize>(a: Order<T, N>, b: Order<T, M>) 
    -> Order<T, { N + M }> { /*...*/ }

but this code won't compile and I don't know how I should do it or whether to do it like this. I think in theory, doing this calculation on const generics is perfectly fine, though, because + of isize is a const fn, right?

So in summary, the problems seem to be these two:

  1. How to allow some transitivity on nested generic type?
  2. How to do calculations on const generics?

If you have any other comments, please let me know as well. Thanks!

Fwiw, this code compiles on nightly with the (incomplete) generic_const_exprs feature enabled.

#![feature(generic_const_exprs)]

struct Quantity<const LENGTH: i32>(f32);

fn add<const A: i32, const B: i32>(left: Quantity<A>, right: Quantity<B>) -> Quantity<{ A + B }> {
    Quantity(left.0 + right.0)
}

(playground)

The adt_const_params feature also lets you use some sort of Ratio as a const parameter (enabling square roots and such), but playing around on the playground shows that to be very incomplete.

1 Like

Is it correct to say we only have a finite # of units? For example, say we only deal with: (meter, gram, second).

Then we can encode velocity = (meter / second) as (1, 0, -1)
area = (meter^2) = (2, 0, 0)
etc ...

Then we can encode the numbers via typenum - Rust so basically a (typenum, typenum, typenum) would encode the "type" of the measurement.

2 Likes

Yes! We have only 7 base units fortunately.

And the typenum crate seems a perfect fit! I will take a deeper look at it. Big thanks for the suggestion!

1 Like

Also check out the uom crate for an existing implementation of dimensional analysis using typenum.

7 Likes

Yeah, I saw that before. It's too heavy for me. And it's based on quantities, but I want to compose units. It's a great crate, though. Presumably, my intended crate and this crate will complement each other.

Fortunately and unfortunately, I think this crate dimensioned has already done what I want to do. Amazing!

And thanks everyone!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.