qtty_core/unit.rs
1//! Unit types and traits.
2
3use crate::dimension::{Dimension, Dimensionless, DivDim};
4use crate::Quantity;
5use core::fmt::{Debug, Display, Formatter, Result};
6use core::marker::PhantomData;
7
8/// Trait implemented by every **unit** type.
9///
10/// * `RATIO` is the conversion factor from this unit to the *canonical scaling unit* of the same dimension.
11/// Example: if metres are canonical (`Meter::RATIO == 1.0`), then kilometres use `Kilometer::RATIO == 1000.0`
12/// because `1 km = 1000 m`.
13///
14/// * `SYMBOL` is the printable string (e.g. `"m"` or `"km"`).
15///
16/// * `Dim` ties the unit to its underlying [`Dimension`].
17///
18/// # Invariants
19///
20/// - Implementations should be zero-sized marker types (this crate's built-in units are unit structs with no fields).
21/// - `RATIO` should be finite and non-zero.
22pub trait Unit: Copy + PartialEq + Debug + 'static {
23 /// Unit-to-canonical conversion factor.
24 const RATIO: f64;
25
26 /// Dimension to which this unit belongs.
27 type Dim: Dimension;
28
29 /// Printable symbol, shown by [`core::fmt::Display`].
30 const SYMBOL: &'static str;
31}
32
33/// Unit representing the division of two other units.
34///
35/// `Per<N, D>` corresponds to `N / D` and carries both the
36/// dimensional information and the scaling ratio between the
37/// constituent units. It is generic over any numerator and
38/// denominator units, which allows implementing arithmetic
39/// generically for all pairs without bespoke macros.
40#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
41pub struct Per<N: Unit, D: Unit>(PhantomData<(N, D)>);
42
43impl<N: Unit, D: Unit> Unit for Per<N, D> {
44 const RATIO: f64 = N::RATIO / D::RATIO;
45 type Dim = DivDim<N::Dim, D::Dim>;
46 const SYMBOL: &'static str = "";
47}
48
49impl<N: Unit, D: Unit> Display for Quantity<Per<N, D>> {
50 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
51 write!(f, "{} {}/{}", self.value(), N::SYMBOL, D::SYMBOL)
52 }
53}
54
55/// Zero-sized marker type for dimensionless quantities.
56///
57/// `Unitless` represents a dimensionless unit with a conversion ratio of 1.0
58/// and an empty symbol. It is used to model the result of simplifying same-unit
59/// ratios (e.g., `Meters / Meters`) into a plain "number-like" `Quantity<Unitless>`.
60///
61/// Unlike a type alias to `f64`, this is a proper zero-sized type, which ensures
62/// that only explicitly constructed `Quantity<Unitless>` values are treated as
63/// dimensionless, not bare `f64` primitives.
64#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
65pub struct Unitless;
66
67impl Unit for Unitless {
68 const RATIO: f64 = 1.0;
69 type Dim = Dimensionless;
70 const SYMBOL: &'static str = "";
71}
72
73impl Display for Quantity<Unitless> {
74 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
75 write!(f, "{}", self.value())
76 }
77}
78
79/// Trait for simplifying composite unit types.
80///
81/// This allows reducing complex unit expressions to simpler forms,
82/// such as `Per<U, U>` to `Unitless` or `Per<N, Per<N, D>>` to `D`.
83pub trait Simplify {
84 /// The simplified unit type.
85 type Out: Unit;
86 /// Convert this quantity to its simplified unit.
87 fn simplify(self) -> Quantity<Self::Out>;
88}
89
90impl<U: Unit> Simplify for Quantity<Per<U, U>> {
91 type Out = Unitless;
92 /// ```rust
93 /// use qtty_core::length::Meters;
94 /// use qtty_core::{Quantity, Simplify, Unitless};
95 ///
96 /// let ratio = Meters::new(1.0) / Meters::new(2.0);
97 /// let unitless: Quantity<Unitless> = ratio.simplify();
98 /// assert!((unitless.value() - 0.5).abs() < 1e-12);
99 /// ```
100 fn simplify(self) -> Quantity<Unitless> {
101 Quantity::new(self.value())
102 }
103}
104
105impl<N: Unit, D: Unit> Simplify for Quantity<Per<N, Per<N, D>>> {
106 type Out = D;
107 fn simplify(self) -> Quantity<D> {
108 Quantity::new(self.value())
109 }
110}