#dimension #unit #const #compile-time #validation #no-alloc #time-unit

no-std const-units

A library that lets you check the dimensions of your quantities at compile time and run time

4 releases

0.1.3 Dec 14, 2023
0.1.2 Dec 14, 2023
0.1.1 Dec 6, 2023
0.1.0 Dec 3, 2023

#1519 in Rust patterns

MIT license

56KB
1K SLoC

const-units

A library that lets you validate the dimensions of your quantities at compile time and at runtime.

WARNING: Uses the experimental features generic_const_exprs and adt_const_params which are known to cause bugs. Disable the const feature if you're only interested in runtime checking.

Compile time example

Okay

use const_units::si; // Parses units at compile time
use const_units::Quantity; // Represents a number with a dimension

// Input attometers, return square attometers
fn square_dist(
    x: Quantity<f64, { si("am") }>,
    y: Quantity<f64, { si("am") }>,
) -> Quantity<f64, { si("am^2") }> {
    x.powi::<2>() + y.powi::<2>()
}

// Input attometers, return meters
fn distance(
    x: Quantity<f64, { si("am") }>,
    y: Quantity<f64, { si("am") }>,
) -> Quantity<f64, { si("m") }> {
    square_dist(x, y)
        .powf::<{ (1, 2) }>() // `(1, 2)` represents 1/2
        .convert_to::<{ si("m") }>()
}

assert_eq!(
    distance(Quantity(3.), Quantity(4.)),
    Quantity(0.000_000_000_000_000_005)
);

Broken

# use const_units::{Quantity, units::{meter, second, DIMENSIONLESS}};
fn sum(
    x: Quantity<f64, { meter }>,
    y: Quantity<f64, { second }>,
) -> Quantity<f64, DIMENSIONLESS> {
    x + y // You can't add meters and seconds
}

Run time example

Requires the dyn feature

use const_units::si;
use const_units::DynQuantity; // A quantity with dynamic units stored at runtime alongside the number
use const_units::InconsistentUnits; // The error returned when inconsistent units are used together

fn distance(
    x: DynQuantity<f64>,
    y: DynQuantity<f64>,
) -> DynQuantity<f64> {
    // The addition operator will panic if the units are inconsistent
    (x.powi(2) + y.powi(2))
        .powf((1, 2))
        .convert_to(si("m"))
        .unwrap()
}

fn distance_checked(
    x: DynQuantity<f64>,
    y: DynQuantity<f64>,
) -> Result<DynQuantity<f64>, InconsistentUnits> {
    // You can use checked operators to avoid panicking
    x.powi(2)
        .checked_add(y.powi(2))?
        .powf((1, 2))
        .convert_to(si("m"))
}

assert_eq!(
    distance_checked(DynQuantity(3., si("am")), DynQuantity(4., si("am"))),
    Ok(DynQuantity(0.000_000_000_000_000_005, si("m"))),
);
assert_eq!(
    distance_checked(DynQuantity(3., si("m")), DynQuantity(4., si("m"))),
    Ok(DynQuantity(5., si("m"))),
);
assert!(distance_checked(DynQuantity(3., si("m")), DynQuantity(4., si("s"))).is_err());

no-std

The std feature can be disabled to allow the crate to function in no-std environments. Doing so will remove convenience methods to convert quantities directly to strings as well as prevent errors from implementing std::error::Error.

Dependencies

~650KB
~14K SLoC