Diman
Diman is a library for zero-cost compile time unit checking.
use ;
let v1 = get_velocity;
let v2 = get_velocity;
assert_eq!;
Let's try to assign add quantities with incompatible units:
use ;
let time = seconds;
let length = 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 }>`
Disclaimer
Diman is implemented using Rust's const generics feature. 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 will change.
If you cannot use unstable Rust for your project or require a stable library for your project, consider using uom or dimensioned, both of which do not require any experimental features and are much more mature libraries in general.
Features
- Invalid operations between physical quantities turn into compile errors.
- Newly created quantities are automatically converted to an underlying base representation. This means that the used types are quantities (such as
Length) instead of concrete units (such asmeters) which makes for more meaningful code. - Systems of units and quantities can be user defined via the
unit_system!macro. This gives the user complete freedom over the choice of quantities and makes them part of the user's library, so that arbitrary new methods can be implemented on them. f32andf64float storage types (behind thef32andf64feature gate respectively).- Vector storage types via
glam(behind theglam-vec2,glam-vec3,glam-dvec2andglam-dvec3features). - Serialization and Deserialization via
serde(behind theserdefeature gate). - HDF5 support using
hdf5-rs(behind thehdf5feature gate). - Quantities can be sent via MPI using
mpi(behind thempifeature gate). - Random quantities can be generated via
rand(behind therandfeature gate).
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 Quantity<S, D> struct, where 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 dimension type would look as follows:
use dimension;
Addition and subtraction of two quantities is only allowed when the D type is the same. During multiplication of two quantities, all the entries of the two dimension are added. This functionality is implemented automatically by the #[diman] macro.
Note: This macro is currently not a derive macro for the Mul/Div traits because the dimension multiplication and division are required to be const. While this is possible, it would require using yet another unstable feature const_trait_impl.
Using the above Dimension type, we can define our own quantity type and a corresponding set of physical quantities and dimensions using the unit_system macro:
use unit_system;
use dimension;
unit_system!;
use f64::;
fast_enough;
This will define the Quantity type and implement all the required traits and methods.
Here, def defines Quantities, which are concrete types, unit defines units, which are methods on the corresponding quantities and constant defines constants. The macro also accepts more complex definitions such as def EnergyRatePerVolume = (Energy / Time) / Volume.
The definitions do not have to be in any specific order.
The Quantity type
The macro will automatically implement numerical traits such as Add, Sub, Mul, and various other methods of the underlying storage type for Quantity<S, ...>.
Quantity should behave just like its underlying storage type whenever possible and allowed by the dimensions.
For example:
- Addition of
Quantity<Float, D>andFloatis possible if and only ifDis dimensionless. Quantityimplements the dimensionless methods ofS, such asabsfor dimensionless quantities.- It implements
DereftoSif and only ifDis dimensionless. Debugis implemented and will print the quantity in its representation of the "closest" unit. For exampleLength::meters(100.0)would be debug printed as0.1 km. If printing in a specific unit is required, conversion methods are available for each unit (such asLength::in_meters)..value()provides access to the underlying storage type of a dimensionless quantity..value_unchecked()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!- Similarly, new quantities can be constructed from storage types using
Vec::new_unchecked. This is also not unit-safe.
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, cbrt as well as powi.
Quantity products and quotients
Sometimes, intermediate types in computations are quantities that doesn'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 Product and Quotient types are provided:
use ;
use ;
let x: = meters * seconds;
let y: = square_meters;
let z: = meters / seconds;
Serde
Serialization and deserialization of the units is provided via serde if the serde feature gate is enabled:
use ;
use ;
let params: Parameters =
from_str.unwrap;
assert_eq!
Rand
Random quantities can be generated via rand
use Rng;
use Length;
let mut rng = thread_rng;
for _ in 0..100