#units #no-alloc #quantities #unit #dimensional-analysis #no-std

no-std shrewnit

A simple, extendable, no_std, no_alloc, and 100% Rust units library

2 releases

new 0.1.1 Mar 4, 2025
0.1.0 Mar 4, 2025

#397 in Science

Download history 207/week @ 2025-02-28

207 downloads per month

MIT license

46KB
694 lines

Shrewnit

Simple, stable, extendible units.

Shrewnit is a 100% stable and no_std Rust units library. Shrewnit has support for adding custom unit types.

Note

Shrewnit deviates from SI in one regard: angle is a base dimension. This means that the units of torque are not Nm, they are Nm/rad or J/rad.

Usage

Shrewnit is a type-per-dimension units library, meaning every dimension gets its own type. Each unit has its own type as well, but they are only used for conversions and initialization of quantities.

Creating Quantities

Quantities can be created in two ways: multiplication and ScalarExt. Multiplication is techinically more correct (quantities are defined as the product of a scalar and a unit), but some may find ScalarExt easier to read.

Multiplication with unit type:

let distance = 1.0 * Inches;

let distance = Inches * 1.0;

ScalarExt:

let distance = 1.0.inches();

Unit Math

Quantities can be multiplied and divided by un-united scalars, but not added or subtracted by un-united scalars.

let mut quantity = 1.0 * Seconds * 2.0;
quantity /= 4.0;

Additional operations are supported depending on the dimension of the quantity. For example, multiplying a LinearVelocity with a Time will result in a Length.

let time = 5.0 * Seconds;
let change_in_velocity = 60.0 * MilesPerHour;

let acceleration = change_in_velocity / time;

Accessing the Value

To get the value of a dimension, use the Dimension trait's to function.

let time = 5.0 * Seconds;

println!("{}", time.to::<Minutes>());

If you prefer your code to read like English, you can use the to! macro.

let time = 5.0 * Seconds;

println!("{}", to!(time in Minutes));

Custom Units and Dimensions

Advanced users may want to add custom units to a dimension, or entirely new dimensions.

Custom dimensions

Use the dimension! macro to create new dimensions. If you need more example usages, this is the macro used internally by Shrewnit to create all dimension and unit types.


shrewnit::dimension!(
    /// Your custom dimension type
    pub MyCustomDimension {
        // Shrewnit uses standard SI units as canonical units. This isn't required. Do whatever you feel like.
        canonical: MyStandardSiUnit,

        // Conversion can be read as "one MyStandardSiUnit per canonical unit"
        MyStandardSiUnit: 1.0 per canonical,
        // Conversion can be read as "two MyHalfUnits per canonical unit"
        MyHalfUnit: 2.0 per canonical,
        // Conversion can be read as "one MyDoubleUnits per two canonical units"
        MyDoubleUnit: per 2.0 canonical,
    } where {
        // Optional operations block.
        // Self </ or *> <other or same dimension type> => <output dimension type> in <output units>
        Self / SomeOtherDimension => ACompletelyDifferentDimension in SomeUnit,
    }
);

This will create the dimension type, the unit types, and any necessary implementations.

Custom Units

Custom units for existing dimensions can be created by manually implementing the UnitOf trait for a type.

struct MyCustomUnitOfLength;
impl<S: Scalar> UnitOf<S, Length<S>> for MyCustomUnitOfLength {
    fn to_canonical(converted: S) -> S {
        converted / S::from_f64(2.0).unwrap()
    }
    fn from_canonical(canonical: S) -> S {
        canonical * S::from_f64(2.0).unwrap()    }
}

You can also use the simple_unit! macro in order to streamline simple conversions like this.

shrewnit::simple_unit!(
    pub MyCustomUnitOfLength of dimension Length = per 2.0 canonical
);

The conversions will be in terms of the dimension's canonical unit. The canonical unit for all Shrewnit measures are the standard SI unit. If you do not know what this is, go to the definition of the dimension. The canonical unit is the one marked with canonical: <unit>.

dimension!(
    pub Torque {
        si: NewtonMeters,
        // This is our canonical unit.
        canonical: NewtonMeters,
...

You can also base your unit conversions off of existing units. Unfortunately, you cannot do this using simple_unit!.

struct HalfInches;
impl<S: Scalar> UnitOf<S, Length<S>> for HalfInches {
    fn to_canonical(converted: S) -> S {
        Inches::to_canonical(converted) * S::from_f64(2.0).unwrap()
    }
    fn from_canonical(canonical: S) -> S {
        Inches::from_canonical(canonical * S::from_f64(2.0).unwrap())
    }
}

FAQ

Where does the name come from?

The name is inspired by the etrsucan shrew, the worlds smallest mammal.

What does this library depend on?

Shrewnit depends on one crate: num-traits. Despite this, Shrewnit is 100% Rust, no_std, libm, and alloc free!

Dependencies

~240KB