Diman is a library for zero-cost compile time unit checking.
```rust use diman::si::f64::{Length, Time, Velocity};
fn get_velocity(x: Length, t: Time) -> Velocity { x / t }
let v1 = getvelocity(Length::kilometers(36.0), Time::hours(1.0)); let v2 = getvelocity(Length::meters(10.0), Time::seconds(1.0));
assert_eq!(v1, v2); ```
Let's try to assign add quantities with incompatible units: ```rust compile_fail 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:
text
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 }>
```
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.
Length
) instead of concrete units (such as meters
) which makes for more meaningful code.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.f32
and f64
float storage types (behind the f32
and f64
feature gate respectively).glam
(behind the glam-vec2
, glam-vec3
, glam-dvec2
and glam-dvec3
features).serde
(behind the serde
feature gate).hdf5-rs
(behind the hdf5
feature gate).mpi
(behind the mpi
feature gate).rand
(behind the rand
feature gate).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:
```rust
use diman::dimension;
pub struct Dimension {
pub length: i32,
pub time: i32,
pub mass: i32,
pub temperature: i32,
pub current: i32,
pub amountofsubstance: i32,
pub luminousintensity: i32,
}
``
Addition and subtraction of two quantities is only allowed when the
Dtype 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 [
consttrait_impl`](https://github.com/rust-lang/rust/issues/67792).
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:
```rust ignore
use diman::unit_system; use diman::dimension;
pub struct Dimension { pub length: i32, pub time: i32, }
unitsystem!( Quantity, Dimension, [ def Length = { length: 1 }, def Time = { time: 1 }, def Velocity = Length / Time, unit (meters, "m") = Length, unit (kilometers, "km") = 1000.0 * meters, unit (seconds, "s") = 1.0 * Time, unit hours = 3600 * seconds, unit meterspersecond = meters / seconds, unit kilometersperhour = kilometers / hours, constant MYFAVORITEVELOCITY = 1000 * metersper_second, ] );
use f64::{Length, Time, Velocity, MYFAVORITEVELOCITY};
fn fastenough(x: Length, t: Time) { let vel = x / t; if vel > MYFAVORITEVELOCITY { println!("{} is definitely fast enough!", vel.inkilometersperhour()); } }
fast_enough(Length::kilometers(100.0), Time::hours(0.3)); ```
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 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>
and Float
is possible if and only if D
is dimensionless.
* Quantity
implements the dimensionless methods of S
, such as abs
for dimensionless quantities.
* It implements Deref
to S
if and only if D
is dimensionless.
* Debug
is implemented and will print the quantity in its representation of the "closest" unit. For example Length::meters(100.0)
would be debug printed as 0.1 km
. If printing in a specific unit is required, conversion methods are available for each unit (such as Length::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
.
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:
rust
use diman::si::f64::{Length, Time, Velocity, Area};
use diman::{Product, Quotient};
let x: Product<(Length, Time)> = Length::meters(10.0) * Time::seconds(2.0);
let y: Product<(Length, Time, Velocity)> = Area::square_meters(5.0);
let z: Quotient<Length, Time> = Length::meters(10.0) / Time::seconds(2.0);
Serialization and deserialization of the units is provided via serde
if the serde
feature gate is enabled:
```rust
use diman::si::f64::{Length, Velocity};
use serde::{Serialize, Deserialize};
struct Parameters { mylength: Length, myvel: Velocity, }
let params: Parameters = serdeyaml::fromstr(" mylength: 100 m myvel: 10 m s^-1 ").unwrap(); asserteq!( params, Parameters { mylength: Length::meters(100.0), myvel: Velocity::metersper_second(10.0), } ) ```
Random quantities can be generated via rand
```rust
use rand::Rng;
use diman::si::f64::Length;
let mut rng = rand::threadrng(); for _ in 0..100 { let x = rng.genrange(Length::meters(0.0)..Length::kilometers(1.0)); assert!(Length::meters(0.0) <= x); assert!(x < Length::meters(1000.0)); } ```