danwi 0.4.2

Zero-cost dimensional analysis library with SI units, compile-time checking, and no_std support
Documentation
//! [`Quantity`], a scalar tagged with a compile-time dimension.

use crate::{dimension::Dimensions, scalar::Scalar};
use core::marker::PhantomData;

mod cmp;
mod convert;
mod fmt;
mod ops;

pub mod kind;

pub use fmt::UnitDisplay;

/// A scalar quantity of a given [`Dimensions`], stored as its value in the
/// SI base unit.
///
/// `#[repr(transparent)]` with a single scalar field: a `Quantity<S, D>`
/// has the same size and alignment as `S`, and the dimension is phantom
/// data. Because values are always in base units, arithmetic and
/// comparisons are plain scalar ops; conversion only happens when
/// constructing from a literal and when reading out via [`to`](Self::to)
/// or [`display_as`](Self::display_as).
///
/// The third parameter is the quantity's [`kind`], defaulting to
/// [`Anon`](kind::Anon); it distinguishes quantities whose dimensions
/// coincide but whose meanings differ, and stays invisible until needed.
///
/// Mismatched dimensions are a compile error:
///
/// ```compile_fail
/// use danwi::prelude::*;
///
/// let nonsense = 1.0 * m + 1.0 * s; // cannot add a length to a time
/// ```
#[repr(transparent)]
pub struct Quantity<S, D, K = kind::Anon>
where
    S: Scalar,
    D: Dimensions,
{
    pub(crate) value: S,
    _phantom: PhantomData<(D, K)>,
}

impl<S, D, K> Quantity<S, D, K>
where
    S: Scalar,
    D: Dimensions,
{
    /// Wrap a raw scalar already expressed in the SI base unit.
    ///
    /// This is the one low-level constructor all other construction paths
    /// go through. End users usually prefer `scalar * unit` syntax or
    /// [`new`](Self::new).
    #[inline(always)]
    pub const fn from_base(value: S) -> Self {
        Self {
            value,
            _phantom: PhantomData,
        }
    }

    /// The value in its SI base unit.
    #[inline(always)]
    pub const fn value(&self) -> S {
        self.value
    }

    /// Reinterpret the quantity as another kind, same dimension and value.
    ///
    /// This is the explicit escape hatch of the kind system: entering a
    /// named kind and mixing kinds both require spelling out the intent.
    #[inline(always)]
    pub const fn cast_kind<K2>(self) -> Quantity<S, D, K2> {
        Quantity {
            value: self.value,
            _phantom: PhantomData,
        }
    }

    /// Drop back to the anonymous kind, e.g. to multiply.
    #[inline(always)]
    pub const fn erase_kind(self) -> Quantity<S, D, kind::Anon> {
        self.cast_kind()
    }
}

// Manual `Copy`/`Clone`: the derive would add `D: Clone` which isn't
// necessary since `D` is only carried as `PhantomData`.
impl<S, D, K> Clone for Quantity<S, D, K>
where
    S: Scalar,
    D: Dimensions,
{
    fn clone(&self) -> Self {
        *self
    }
}

impl<S, D, K> Copy for Quantity<S, D, K>
where
    S: Scalar,
    D: Dimensions,
{
}

impl<S, D, K> core::fmt::Debug for Quantity<S, D, K>
where
    S: Scalar,
    D: Dimensions,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "Quantity({:?})", self.value)
    }
}

// Compile-time tripwires: the zero-cost promise is only true if the wrapper
// is exactly the same shape as its inner scalar. If anyone ever adds a
// field or breaks `#[repr(transparent)]`, the crate refuses to compile.
#[cfg(feature = "f32")]
const _: () = assert!(
    core::mem::size_of::<Quantity<f32, crate::dimension::Dimensionless>>()
        == core::mem::size_of::<f32>()
);

#[cfg(feature = "f64")]
const _: () = assert!(
    core::mem::size_of::<Quantity<f64, crate::dimension::Dimensionless>>()
        == core::mem::size_of::<f64>()
);