Your own units¶
Three macros declare markers of your own: unit!, scale! and origin!. Each line is data, literal values only, for two readers:
graph LR
L["a unit! / scale! / origin! line<br/>in your crate"] --> C["the compiler<br/>impl Unit, impl FixedScale, impl Origin"]
L --> W["WireWeaver's walker<br/>reads the same line from source"]
W --> M["the API model<br/>clients know the unit"]
So a tool that only has your device's API model still knows what rpm means. A hand-written impl Unit compiles, but WireWeaver refuses an API that uses it.
unit!¶
exact_units::unit! {
/// Revolutions per minute: 2π rad / 60 s, exactly.
pub struct Rpm = "rpm", [rad 1, s -1], Exact(1 / 30, pi 1);
/// A count of histogram buckets, a base of its own.
pub struct Bucket = "bucket", counted;
}
use exact_units::{Fixed, Unit, prefix::One};
let speed = Fixed::<Rpm, One, u8>::new(60);
assert_eq!(speed.to_string(), "60 rpm");
// 60 rpm in rad/s
assert!((Rpm::EXPR.factor() * 60.0 - core::f64::consts::TAU).abs() < 1e-12);
assert_eq!(Bucket::DIM, exact_units::counted!(Bucket));
A line is <attributes> <visibility> struct <Name> = "<symbol>", <dimension>[, <scale>];:
- Dimension: exponents of
s,m,kg,a,k,mol,cd,rad,bit([kg 1, m 2, s -3, a -1]is a volt,[]a plain number), orcountedfor a base of its own, named by your crate and the marker. - Scale, coherent when left out:
Pow10(-3), orExact(num / den, pow10 e, pow2 e, pi e). - Kind, after the dimension:
kind "resistance"makes a unit that doesn't convert to others of its dimension. - Provenance, after the scale:
conventional("NIST SP 811", 2008)marks a unit fixed by a measured property (inHg at 32 °F) rather than a definition.
A float factor is not a scale: an exact one is a Magnitude, and a factor known only at run time (a calibration) is a value.
scale!¶
A step of your own: a gyro's 70 mdps per LSB, a quarter.
exact_units::scale! {
/// One step of a gyro: 70/1000 of the unit.
pub struct Mdps70 = "", Exact(70 / 1000);
/// A quarter, 2⁻².
pub struct Quarter = "", Pow2(-2);
}
use exact_units::{FixedScale, Magnitude};
assert_eq!(Mdps70::SCALE, Magnitude::ratio(7, 100));
assert_eq!(Quarter::SCALE.to_f64(), 0.25);
origin!¶
An origin for points: from the unit's zero, or above another origin.
use exact_units::{Fixed, Magnitude, Offset, Point, origin, origins::Celsius, prefix::One, units::Kelvin};
origin! {
/// Degrees Rømer: water freezes at 7.5 °Rø and boils at 60 °Rø, so a degree is 40/21 K and
/// 0 °Rø is 273.15 − 7.5 · 40/21 = 108723/420 K.
pub struct Romer { unit: Kelvin, symbol: "°Rø", offset: Offset::ratio(108723, 420), step: Magnitude::ratio(40, 21) }
}
type DegRo = Point<Romer, Fixed<Kelvin, exact_units::prefix::Rational<40, 21>, i16>>;
// water boils
assert_eq!(DegRo::from_raw(60).to::<Celsius, One, i16>().map(|p| p.raw()), Some(100));
After offset come, in this order and each optional: step (1), reference (the unit's zero) and modulus (Linear, or Modulo<..> for an angle that wraps).
Counted bases from other crates¶
A counted marker's dimension is named by its crate and its name: counted!(Bucket) in your crate is a different base from a Bucket in another crate. Two APIs can't mix their buckets by accident.