### 3 releases (breaking)

0.4.0 | Jan 7, 2024 |
---|---|

0.3.0 | Dec 1, 2023 |

0.2.0 | May 27, 2023 |

#**78** in Science

**30** downloads per month

**MIT/Apache**

49KB

803 lines

# Diman

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

`#` `#!``[``feature``(``generic_const_exprs``)``]`
`use` `diman``::``si``::``f64``::``{`Length`,` Time`,` Velocity`}``;`
`fn` `get_velocity``(``x``:` Length, `t``:` Time`)`` ``->` Velocity `{`
x `/` t
`}`
`let` v1 `=` `get_velocity``(``Length``::`kilometers`(``36.``0``)``,` `Time``::`hours`(``1.``0``)``)``;`
`let` v2 `=` `get_velocity``(``Length``::`meters`(``10.``0``)``,` `Time``::`seconds`(``1.``0``)``)``;`
`assert_eq!``(`v1`,` v2`)``;`

Let's try to assign add quantities with incompatible dimensions:

`use` `diman``::``si``::``f64``::``{`Length`,` Time`}``;`
`let` time `=` `Time``::`seconds`(``1.``0``)``;`
`let` length `=` `Length``::`meters`(``10.``0``)``;`
`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 }>`
`

## Disclaimer

Diman is implemented using Rust's const generics feature. While

has been stabilized since Rust 1.51, Diman uses more complex generic expressions and therefore requires the two currently unstable features `min_const_generics`

and `generic_const_exprs`

.`adt_const_params`

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

If you cannot use unstable Rust for your project or require a stable library, consider using

or `uom`

, both of which do not require any experimental features and are much more mature libraries in general.`dimensioned`

## 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

) instead of concrete units (such as`Length`

) which makes for more meaningful code.`meters` - Systems of dimensions and units can be user defined via the

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.`unit_system!` - The

features allows the usage of quantities and units with rational exponents.`rational-dimensions`

and`f32`

float storage types (behind the`f64`

and`f32`

feature gate respectively).`f64`- Vector storage types via

(behind the`glam`

,`glam-vec2`

,`glam-vec3`

and`glam-dvec2`

features).`glam-dvec3` - Serialization and Deserialization via

(behind the`serde`

feature gate, see the official documentation for more info).`serde` - HDF5 support using

(behind the`hdf5-rs`

feature gate).`hdf5` - Quantities implement the

trait so that they can be sent via MPI using`Equivalence`

(behind the`mpi`

feature gate).`mpi` - Random quantities can be generated via

(behind the`rand`

feature gate, see the official documentation for more info).`rand`

## Design

Diman aims to make it as easy as possible to add compile-time unit safety to Rust code. Physical quantities are represented by the

struct, where `Quantity <S, D>`

`S`

is the underlying storage type (`f32`

, `f64`

, ...) and `D`

is the dimension of the quantity. For example, in order to represent the SI system of units, the quantity type would be defined using the `unit_system!`

macro as follows:`#!``[``allow``(``incomplete_features``)``]`
`#!``[``feature``(``generic_const_exprs``,` adt_const_params`)``]`
`use` `diman``::`unit_system`;`
`unit_system!``(`
quantity_type Quantity`;`
dimension_type Dimension`;`
dimension Length`;`
dimension Time`;`
dimension Mass`;`
dimension Temperature`;`
dimension Current`;`
dimension AmountOfSubstance`;`
dimension LuminousIntensity`;`
`)``;`

The first two statements imply that the macro should define a

type, which is user-facing, and a `Quantity`

type, which is used only internally and will surface in compiler error messages.
The macro will automatically implement all the required traits and methods for the `Dimension`

type, such that addition and subtraction of two quantities is only allowed for quantities with the same `Quantity`

type. During multiplication of two quantities, all the entries of the two dimensions are added. See below for a more comprehensive list of the implemented methods on `Dimension`

.`Quantity`

The

macro also allows defining derived dimensions and units:`unit_system!`

`#!``[``allow``(``incomplete_features``)``]`
`#!``[``feature``(``generic_const_exprs``,` adt_const_params`)``]`
`use` `diman_unit_system``::`unit_system`;`
`unit_system!``(`
quantity_type Quantity`;`
dimension_type Dimension`;`
dimension Length`;`
dimension Time`;`
dimension Velocity `=` Length `/` Time`;`
`#``[``prefix``(``kilo``,` milli`)``]`
`#``[``symbol``(``m``)``]`
`#``[``base``(``Length``)``]`
unit meters`;`
`#``[``base``(``Time``)``]`
unit seconds`;`
unit hours`:` Time `=` `3600` `*` seconds`;`
unit meters_per_second`:` Velocity `=` meters `/` seconds`;`
unit kilometers_per_hour`:` Velocity `=` kilometers `/` hours`;`
constant `MY_FAVORITE_VELOCITY` `=` `1000` `*` kilometers_per_hour`;`
`)``;`
`use` `f64``:``:``{`Length`,` Time`,` Velocity`,` `MY_FAVORITE_VELOCITY``}``;`
`fn` `fast_enough``(``x``:` Length, `t``:` Time`)`` ``{`
`let` vel `=` x `/` t`;`
`if` vel `>` `MY_FAVORITE_VELOCITY` `{`
`println!``(``"``{}` m/s is definitely fast enough!`"``,` vel`.``in_meters_per_second``(``)``)``;`
`}`
`}`
`fast_enough``(``Length``::`kilometers`(``100.``0``)``,` `Time``::`hours`(``0.``3``)``)``;`

Here,

defines Quantities, which are concrete types, `dimension`

defines units, which are methods on the corresponding quantities and `unit`

defines constants.
Dimensions without a right hand side are base dimensions (such as length, time, mass, temperature, ... in the SI system of units), whereas dimensions with a right hand side are derived dimensions.
The same thing holds for units - every unit is either a base unit for a given base dimension (denoted by the `constant`

attribute), or derived from base units and other derived units. Base units have the special property that the internal representation of the quantity will be in terms of the base unit (for example, a stored value `#``[``base``(``...``)``]`

for a quantity with a `1.``0`

dimension corresponds to `Length`

in the above definitions).
Other than this, there are no differences between base dimensions and dimensions or base units and units and they can be treated equally in user code.
The macro also accepts more complex expressions such as `meter`

.
The definitions do not have to be in any specific order.`dimension Energy = Mass (Length / Time)^2`

## The Quantity type

The macro will automatically implement numerical traits such as

, `Add`

, `Sub`

, and various other methods of the underlying storage type for `Mul`

.
`Quantity <S, .`

`..`

`>`

`Quantity`

should behave just like its underlying storage type whenever possible and allowed by the dimensions.
For example:- Addition of

and`Quantity``<`Float, D`>`

is possible if and only if`Float`

is dimensionless.`D`

implements the dimensionless methods of`Quantity`

, such as`S`

for dimensionless quantities.`abs`- It implements

to`Deref`

if and only if`S`

is dimensionless.`D`

is implemented and will print the quantity in its representation of the "closest" unit. For example`Debug`

would be debug printed as`Length`meters`::``(``100.``0``)`

. If printing in a specific unit is required, conversion methods are available for each unit (such as`0.``1`km

).`Length`in_meters`::`

provides access to the underlying storage type of a dimensionless quantity.`.``value``(``)`

provides access to the underlying storage type for all quantities if absolutely required. This is not unit-safe since the value will depend on the unit system!`.``value_unchecked``(``)`- Similarly, new quantities can be constructed from storage types using

. This is also not unit-safe.`Quantity`new_unchecked`::`

Some other, more complex operations are also allowed:

`use` `diman``::``si``::``f64``::``{`Length`,` Volume`}``;`
`let` x `=` `Length``::`meters`(``3.``0``)``;`
`let` vol `=` x`.``cubed``(``)``;`
`assert_eq!``(`vol`,` `Volume``::`cubic_meters`(``27.``0``)``)`

This includes

, `squared`

, `cubed`

, `sqrt`

as well as `cbrt`

.`powi`

## Prefixes

Unit prefixes can automatically be generated with the

attribute for unit statements.
For example`#``[``prefix``(``...``)``]`

`#``[``prefix``(``kilo``,` milli`)``]`
`#``[``symbol``(``m``)``]`
unit meters`;`

will automatically generate the unit

with symbol `meters`

, as well as `m`

and `kilometers`

with symbols `millimeters`

and `km`

corresponding to `mm`

and `1e3 m`

.
For simplicity, the attribute `1e -3 m`

`#``[``metric_prefixes``]`

is provided, which will generate all metric prefixes from `atto``-`

to `exa``-`

automatically.## Aliases

Unit aliases can automatically be generated with the

macro. For example`#``[``alias``(``...``)``]`

`#``[``alias``(``metres``)``]`
unit meters`;`

will automatically generate a unit

that has exactly the same definition as `metres`

. This works with prefixes as expected (i.e. an alias is generated for every prefixed unit).`meters`

## Quantity products and quotients

Sometimes, intermediate types in computations are quantities that don't really have a nice name and are also
not needed too many times. Having to add a definition to the unit system for this case can be cumbersome.
This is why the

and `Product`

types are provided:`Quotient`

`use` `diman``::``si``::``f64``::``{`Length`,` Time`,` Velocity`,` Area`,` Volume`}``;`
`use` `diman``::``{`Product`,` Quotient`}``;`
`fn` `foo``(``l``:` Length, `t``:` Time, `vol``:` Volume`)`` ``->` `Product``<``(`Length, Time, Volume`)``>` `{`
l `*` t `*` vol
`}`
`fn` `bar``(``l``:` Length, `t``:` Time`)`` ``->` `Quotient``<`Length, Time`>` `{`
l `/` t
`}`

## Rational dimensions

The

feature allows using quantities with rational exponents in their base dimensions, as opposed to just integer values. This allows expressing defining dimensions and units such as`rational-dimensions`

`dimension Sorptivity ``=` Length Time`^``(``-``1``/``2``)``;`
unit meters_per_sqrt_second`:` Sorptivity `=` meters `/` seconds`^``(``1``/``2``)``;`

and using them in the usual manner

`let` l `=` `Length``::`micrometers`(``2.``0``)``;`
`let` t `=` `Time``::`milliseconds`(``5.``0``)``;`
`let` sorptivity`:` Sorptivity `=` l `/` t`.``sqrt``(``)``;`

The unit system generated with

supports a superset of features of a unit system generated without them.
Still, this feature should be enabled only when necessary, since the compiler errors in case of dimension mismatches will be harder to read.`rational-dimensions`

`serde`

`serde`

Serialization and deserialization of the units is provided via

if the `serde`

feature gate is enabled:`serde`

`use` `diman``::``si``::``f64``::``{`Length`,` Velocity`}``;`
`use` `serde``::``{`Serialize`,` Deserialize`}``;`
`#``[``derive``(``Serialize``,` Deserialize`,` Debug`,` PartialEq`)``]`
`struct` `Parameters` `{`
`my_length``:` Length,
`my_vel``:` Velocity,
`}`
`let` params`:` Parameters `=`
`serde_yaml``::`from_str`(``"`
my_length: 100 m
my_vel: 10 m s^-1
`"``)``.``unwrap``(``)``;`
`assert_eq!``(`
params`,`
Parameters `{`
my_length`:` `Length``::`meters`(``100.``0``)``,`
my_vel`:` `Velocity``::`meters_per_second`(``10.``0``)``,`
`}`
`)`

`rand`

`rand`

Diman allows generating random quantities via

if the `rand`

feature gate is enabled:`rand`

`use` `rand``::`Rng`;`
`use` `diman``::``si``::``f64``::`Length`;`
`let` `mut` rng `=` `rand``::`thread_rng`(``)``;`
`for` `_` `in` `0``..``100` `{`
`let` x `=` rng`.``gen_range``(``Length``::`meters`(``0.``0``)``..``Length``::`kilometers`(``1.``0``)``)``;`
`assert!``(``Length``::`meters`(``0.``0``)` `<=` x`)``;`
`assert!``(`x `<` `Length``::`meters`(``1000.``0``)``)``;`
`}`

#### Dependencies

~0.3–2.5MB

~58K SLoC