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}