Crate syunit

Source
Expand description


A small library that contains some basic units to help structuring kinematics and robotic programming in rust. The library uses rusts tuple structs to create a compile-time checking of correct unit, variable and function usage.

§Quick introduction

In many functions for kinematics and robotics, it sometimes becomes unclear which type of unit is desired to be used.

/// Relative movement
fn move_distance(dist : f32, vel : f32) {
    // ...
}

/// Absolute movement
fn move_to_position(pos : f32, vel : f32) {
    // ...
}

Even if most code is not as horribly documented, nothing stops a developer from accidently plugging in an absolute distance into a function that takes a relative one. Here comes this library into play:

use syunit::prelude::*;

/// Relative movement
fn move_distance(dist : Radians, vel : RadPerSecond) {
    // ...
}

/// Absolute movement
fn move_to_position(pos : PositionRad, vel : RadPerSecond) {
    // ...
}

Each unit is represented by a f32 enclosed into a tuple struct, making them simple but also their own type!

Why these units are helpful not only for documentation is explained in the flowing chapters:

§Explicit syntax

As rust always prefers explicit syntax, so does this library. The unit types cannot be converted back to a f32 without calling into().

use syunit::prelude::*;

fn requires_f32(value : f32) {
    // ...
}

fn requires_velocity(value : MMPerSecond) {
    // ...
}

// Every unit is created by the tuple struct constructor using a `f32` value
let abs_pos = PositionMM(10.0);

requires_f32(abs_pos);
// error[E0308]: mismatched types
// |
// | requires_f32(abs_pos) // ERROR! => Type `PositionMM` cannot be used as `f32`
// | ------------ ^^^^^ expected `f32`, found `PositionMM`
// | |
// | arguments to this function are incorrect
// |

requires_velocity(abs_pos);
// error[E0308]: mismatched types
// |
// | requires_f32(abs_pos);
// | ------------ ^^^^^ expected `MMPerSecond`, found `PositionMM`
// | |
// | arguments to this function are incorrect
// |

§Operations and automatic type evaluation

The library comes with a lot of implementations in addition to the units, making it possible to do a lot of operations with these units and letting the compiler automatically evaluate the resulting units for you

use syunit::prelude::*;

// Radial / Linear
assert_eq!(RadPerSecond(4.0) * Millimeters(2.0), MMPerSecond(8.0));
assert_eq!(RadPerSecond2(-3.0) * Millimeters(2.0), MMPerSecond2(-6.0));

// Seconds / Hertz
assert_eq!(Hertz(4.0), 1.0 / Seconds(0.25));
assert_eq!(1.0 / Hertz(5.0), Seconds(0.2));

// Time to build up speed
assert_eq!(MMPerSecond(6.0) / MMPerSecond2(2.0), Seconds(3.0));

// Forces
assert_eq!(NewtonMeters(-5.0) / KgMeter2(2.0), RadPerSecond2(-2.5));
assert!((Newtons(3.0) / Kilogramms(1.5) - MMPerSecond2(2000.0)).abs().0 < 0.001);  // Automatic conversion

// ...

Another helpful unit type are Positions, they help differentiating between absolute and relative distances.

use syunit::prelude::*;

// Difference between two positions is a relative distance
assert_eq!(PositionMM(5.0) - PositionMM(3.0), Millimeters(2.0));

// Radial position math
assert_eq!(PositionRad(3.0) + Radians(2.0), PositionRad(5.0));
assert_eq!(PositionRad(3.0) - PositionRad(2.0), Radians(1.0)); 

A very special unit is Seconds, dividing or multipling by it often changes units.

use syunit::prelude::*;

// Travelling a distance of 6mm in 2 seconds gives a velocity of 3mm/s
assert_eq!(Millimeters(6.0) / Seconds(2.0), MMPerSecond(3.0));
// Accelerating to a velocity of 3mm/s in 2 seconds gives an acceleration of 1.5mm/s^2
assert_eq!(MMPerSecond(3.0) / Seconds(2.0), MMPerSecond2(1.5));
// Travelling with 3mm/s for 3 seconds gives a total distance of 9mm
assert_eq!(MMPerSecond(3.0) * Seconds(3.0), Millimeters(9.0));

§Unitsets

There is also a tool for defining functions in a more general way, with the help of UnitSets!

use syunit::prelude::*;

fn get_distance<U : UnitSet>(vel : U::Velocity, time : U::Time) -> U::Distance {
    vel * time      // Compiler automatically checks if the types match
}

fn time_for_dist_accelerating<U : UnitSet>(distance : U::Distance, vel_start : U::Velocity, vel_end : U::Velocity) -> U::Time {
    let vel_avg = (vel_start + vel_end) / 2.0;
    distance / vel_avg
}

assert_eq!(get_distance::<MetricMM>(MMPerSecond(4.0), Seconds(2.0)), Millimeters(8.0));     // Using linear metric mm
assert_eq!(time_for_dist_accelerating::<Rotary>(Radians(3.0), RadPerSecond(2.0), RadPerSecond(4.0)), Seconds(1.0));     // Using rotary units

§Metric and Imperial

The library also includes imperial units and conversions between them.

use syunit::prelude::*;
use syunit::imperial::*;

let millimeters = Millimeters(25.4);

assert_eq!(Meters(1.0), Millimeters(1000.0).into());
assert_eq!(Inches(1.0), Millimeters(25.4).into());

§serde implementation

All the units implement serde::Serialize and serde::Deserialize if the “serde” feature is enabled, which is the case by default.

§Issues and improvements

Please feel free to create issues on the github repo!

Re-exports§

pub use metric::MetricMM;
pub use metric::Rotary;

Modules§

imperial
Imperial units of measurement
macros
Macros for creating units and fast implementations between them
metric
Metric units of measurement and useful UnitSets
prelude
Lazy import of the library

Macros§

additive_unit
Helper macro that implements everything needed to do +,-,+=,-= operations with the unit itself
basic_unit
Implements the basics for a unit
basic_unit_helper
Implements the basics for a unit
derive_units
Implements everything required to form a “derive over time like”-connection between the given units
impl_conversion
Implements conversion between two units
impl_div
Automatically implements division between three units
impl_div_bidir
Identical to impl_div!, however it implements the division in both directions, so
impl_full_conversion
Implements everything required to form a “derive over time like”-connection between the given units
impl_mul
Automatically implements multiplication between three units
impl_mul_bidir
Identical to impl_mul!, however it implements the division in both directions, so
inertia_unit
Automatically implement InertiaUnit for the given unit
position_unit
Helper macro for position units

Structs§

Factor
Represents a certain factor between 0 and 1
Hertz
Represents a freqency in Hertz (or 1 / Seconds)
PositionRad
Represents a position in Radians
RadPerSecond
Represents Radians per second (rad/s)
RadPerSecond2
Represents Radians per second squared (rad/s^2)
RadPerSecond3
Represents Radians per second qubed (rad/s^3)
Radians
Represents Radians (rad)
Seconds
Represents a time in seconds as a f32

Enums§

Direction
Direction of movement

Traits§

AdditiveUnit
Marker traits for units, that can be added and subtracted from themselfs
DerivableUnit
Marker trait for units that can be derived by a variable V to form the result Result
InertiaUnit
A helper trait for calculations with inertia units
IntegrableUnit
Marker trait for units that can be integrated by a variable V to form the result Result
Unit
General trait for all units, defines the basic constraints required to work with units
UnitSet
A set of units that have a strong relationship to each other

Functions§

add_unit_arrays
Add two arrays of units
compare_unit_arrays
Compare two unit arrays
sub_unit_arrays
Subtract two arrays of units