SkillAgentSearch skills...

Diman

Define rust compile time unit systems using const generics

Install / Use

/learn @Tehforsch/Diman
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<!-- cargo-rdme start -->

Diman is a library for zero-cost compile time unit checking.

use diman::si::dimensions::{Length, Time, Velocity};
use diman::si::units::{seconds, meters, kilometers, hours, hour};

fn get_velocity(x: Length<f64>, t: Time<f64>) -> Velocity<f64> {
    x / t
}

let v1 = get_velocity(36.0 * kilometers, 1.0 * hours);
let v2 = get_velocity(10.0 * meters, 1.0 * seconds);

assert_eq!(v1, v2);
assert_eq!(format!("{} km/h", v1.value_in(kilometers / hour)), "36 km/h");

Diman prevents unit errors at compile time:

let time = 1.0 * seconds;
let length = 10.0 * meters;
let sum = length + time;

This results in a compiler error:

let sum = length + time;
                   ^^^^
= note: expected struct `Quantity<_, Dimension { length: 1, time: 0, mass: 0, temperature: 0, current: 0, amount_of_substance: 0, luminous_intensity: 0 }>`
        found struct `Quantity<_, Dimension { length: 0, time: 1, mass: 0, temperature: 0, current: 0, amount_of_substance: 0, luminous_intensity: 0 }>`

While Diman provides a full definition of the SI system of units, it also fully supports defining custom systems of dimensions and units:

diman::unit_system!(
    quantity_type Quantity;
    dimension_type Dimension;

    dimension Information;
    dimension Time;

    dimension Bandwidth = Information / Time;

    #[prefix(kilo, mega)]
    #[base(Information)]
    #[symbol(B)]
    unit byte;

    #[base(Time)]
    #[symbol(s)]
    unit seconds;

    unit hours = 3600 * seconds;
);

fn get_bitrate(information: Information<f64>, time: Time<f64>) -> Bandwidth<f64> {
    information / time
}

get_bitrate(50.0 * megabyte, 2.0 * hours);

Disclaimer

Diman is implemented using Rust's const generics feature. This makes for very readable error messages compared to alternatives which are typically based on typenum. While min_const_generics has been stabilized since Rust 1.51, Diman uses more complex generic expressions and therefore requires the two currently unstable features generic_const_exprs and adt_const_params.

Moreover, Diman is in its early stages of development and APIs might change.

If you cannot use unstable Rust for your project, consider using uom or dimensioned, both of which do not require any experimental features.

Features

  • Invalid operations between physical quantities (adding length and time, for example) turn into compile errors.
  • Newly created quantities are automatically converted to an underlying base representation. This means that the used types are dimensions (such as Length) instead of concrete units (such as meters) which makes for more meaningful code.
  • Systems of dimensions and units can be user defined via the unit_system! macro. This gives the user complete freedom over the choice of dimensions and makes them part of the user's library, so that arbitrary new methods can be implemented on them.
  • The rational-dimensions features allows the usage of quantities and units with rational exponents.
  • f32 and f64 float storage types (behind the f32 and f64 feature gate respectively).
  • The std feature is enabled by default. If disabled, Diman will be a no_std crate, thus suitable for use on embedded devices such as GPU device kernels.
  • The num-traits-libm feature uses libm to provide math functions in no_std environments. While one can use libm in std, the libm implementations are generally slower so this is unlikely to be desirable.
  • Vector storage types via glam (behind the glam-vec2, glam-vec3, glam-dvec2 and glam-dvec3 features).
  • Serialization and Deserialization via serde (behind the serde feature gate, see the official documentation for more info).
  • HDF5 support using hdf5-rs (behind the hdf5 feature gate).
  • Quantities implement the Equivalence trait so that they can be sent via MPI using mpi (behind the mpi feature gate).
  • Random quantities can be generated via rand (behind the rand feature gate, see the official documentation for more info).

The Quantity type

Physical quantities are represented by the Quantity<S, D> struct, where S is the underlying storage type (f32, f64, ...) and D is the dimension of the quantity. Quantity should behave like its underlying storage type whenever allowed by the dimensions.

Arithmetics and math

Addition and subtraction of two quantities is allowed if the dimensions match:

let l = 5.0 * meters + 10.0 * kilometers;

Multiplication and division of two quantities produces a new quantity:

let l = 5.0 * meters;
let t = 2.0 * seconds;
let v: Velocity<f64> = l / t;

Addition and subtraction of a Quantity and a storage type is possible if and only if D is dimensionless:

let d1 = 5.0 * meters;
let d2 = 10.0 * kilometers;
let x = d1 / d2 - 0.5;
let y = 0.5 - d1 / d2;

Quantity implements the dimensionless methods of S, such as sin, cos, etc. for dimensionless quantities:

let d1 = 5.0f64 * meters;
let d2 = 10.0f64 * kilometers;
let angle_radians = (d1 / d2).asin();

Exponentiation and related operations are supported via squared, cubed, powi, sqrt, cbrt:

let length = 2.0f64 * meters;
let area = length.squared();
assert_eq!(area, 4.0 * square_meters);
assert_eq!(area.sqrt(), length);
let vol = length.cubed();
assert_eq!(vol, 8.0 * cubic_meters);
assert_eq!(vol.cbrt(), length);
let foo = length.powi::<4>();

Note that unlike its float equivalent, powi receives its exponent as a generic instead of as a normal function argument. Exponentiation of dimensionful quantities with an non-constant integer is not supported, since the compiler cannot infer the dimension of the return type. However, dimensionless quantities can be raised to arbitrary powers using powf:

let d1 = 2.0f64 * meters;
let d2 = 5.0f64 * kilometers;
let x = (d1 / d2).powf(2.71);

Creation and conversion

New quantities can be created either by multiplying with a unit, or by calling the .new function on the unit:

let d1 = 2.0 * meters;
let d2 = meters.new(2.0);
assert_eq!(d1, d2);

For a full list of the units supported by dimans SI module, see the definitions. Composite units can be defined on the spot via multiplication/division of units:

let v1 = (kilometers / hour).new(3.6);
let v2 = 3.6 * kilometers / hour;
assert_eq!(v1, 1.0 * meters_per_second);
assert_eq!(v2, 1.0 * meters_per_second);

Note that at the moment, the creation of quantities via units defined in this composite way incurs a small performance overhead compared to creation from just a single unit (which is just a single multiplication). This will be fixed once const_fn_floating_point_arithmetic or a similar feature is stabilized.

Conversion into the underlying storage type can be done using the value_in function:

let length = 2.0f64 * kilometers;
assert_eq!(format!("{} m", length.value_in(meters)), "2000 m");

This also works for composite units:

let vel = 10.0f64 * meters_per_second;
assert_eq!(format!("{} km/h", vel.value_in(kilometers / hour)), "36 km/h");

For dimensionless quantities, .value() provides access to the underlying storage types. Alternatively, dimensionless quantities also implement Deref for the same operation.

let d1: Length<f64> = 5.0 * meters;
let d2: Length<f64> = 10.0 * kilometers;
let ratio_value: f64 = (d1 / d2).value();
let ratio_deref: f64 = *(d1 / d2);
assert_eq!(ratio_value, ratio_deref);

Unchecked creation and conversion

If absolutely required, .value_unchecked() provides access to the underlying storage type for all quantities. This is not unit-safe since the return value will depend on the unit system!

let length: Length<f64> = 5.0 * kilometers;
let value: f64 = length.value_unchecked();
assert_eq!(value, 5000.0); // This only holds in SI units!

Similarly, if absolutely required, new quantities can be constructed from storage types using Quantity::new_unchecked. This operation is also not unit-safe!

let length: Length<f64> = Length::new_unchecked(5000.0);
assert_eq!(length, 5.0 * kilometers); // This only holds in SI units!

The combination of value_unchecked and new_unchecked comes in handy when using third party libraries that only takes the raw storage type as argument. As an example, suppose we have a function foo that takes a Vec<f64> and returns a Vec<f64>, and suppose it sorts the numbers or does some other unit safe operation. Then we could reasonably write:

   let lengths: Vec<Length<f64>> = vec![
       1.0 * meters,
       2.0 * kilometers,
       3.0 * meters,
       4.0 * kilometers,
   ];
   let unchecked = lengths.into_iter().map(|x| x.value_unchecked()).collect();
   let fooed = foo(unchecked);
   let result: Vec<_> = fooed
       .into_iter()
       .map(|x| Length::new_unchecked(x))
       .collect();

Debug

Debug is implemented and will print the quantity in its base representation.

let length: Length<f64> = 5.0 * kilometers;
let time: Time<f64> = 1.0 * seconds;
assert_eq!(format!("{:?}", length / time), "5000 m s^-1")

Custom unit systems

The unit_system macro

Diman also provides the unit_system macro for defining custom unit systems for everything that is not covered by SI alone. The macro will add a new quantity type and implement all the required methods and traits to make it usable. As an example, consider the following macro call:

diman::unit_system!(
    quantity_type Quantity;
    dimension_type Dimension;

    dimension Length;
    dimension Time;
    dimensi
View on GitHub
GitHub Stars86
CategoryDevelopment
Updated2mo ago
Forks4

Languages

Rust

Security Score

80/100

Audited on Jan 8, 2026

No findings