Skip to main content

qtty_core/units/volume/
mod.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Volume units.
5//!
6//! The canonical scaling unit for this dimension is the **cubic metre** (`CubicMeter::RATIO == 1.0`).
7//! All other volume units are expressed as exact ratios to cubic metres.
8//!
9//! This module provides:
10//!
11//! - **Metric cubes**: cubic millimetre, cubic centimetre, cubic metre, cubic kilometre.
12//! - **Litre family**: microlitre, millilitre, centilitre, decilitre, litre.
13//! - **Imperial/US**: cubic inch, cubic foot, US gallon, US fluid ounce.
14//!
15//! Volume units can also arise *automatically* from multiplying length × area quantities:
16//!
17//! ```rust
18//! use qtty_core::length::Meters;
19//! use qtty_core::area::SquareMeters;
20//! use qtty_core::volume::CubicMeters;
21//!
22//! let side = Meters::new(3.0);
23//! let face: SquareMeters = side * side;              // direct product
24//! let vol: CubicMeters = (face * side).to();
25//! assert!((vol.value() - 27.0).abs() < 1e-12);
26//! ```
27//!
28//! ## All volume units (default)
29//!
30//! ```rust
31//! use qtty_core::volume::*;
32//!
33//! macro_rules! touch {
34//!     ($T:ty, $v:expr) => {{ let q = <$T>::new($v); let _c = q; assert!(q == q); }};
35//! }
36//!
37//! touch!(CubicMeters, 1.0);    touch!(CubicKilometers, 1.0);
38//! touch!(CubicCentimeters, 1.0); touch!(CubicMillimeters, 1.0);
39//! touch!(Liters, 1.0);         touch!(Milliliters, 1.0);
40//! touch!(Microliters, 1.0);    touch!(Centiliters, 1.0);
41//! touch!(Deciliters, 1.0);
42//! ```
43
44use crate::{Prod, Quantity, Unit};
45use qtty_derive::Unit;
46
47/// Re-export the volume dimension from the dimension module.
48pub use crate::dimension::Volume;
49
50/// Marker trait for any [`Unit`] whose dimension is [`Volume`].
51pub trait VolumeUnit: Unit<Dim = Volume> {}
52impl<T: Unit<Dim = Volume>> VolumeUnit for T {}
53
54/// A composed volume quantity from cubing a length unit.
55///
56/// `CubeOf<L>` is `Quantity<Prod<Prod<L, L>, L>>` — the type produced when
57/// multiplying a [`SquareOf`](super::area::SquareOf) quantity by a further
58/// length quantity. Since metric area unit types are `Prod` aliases and are
59/// registered as [`BuiltinUnit`](crate::unit_arithmetic::BuiltinUnit), the
60/// intermediate multiplication just works.
61///
62/// # Examples
63///
64/// ```rust
65/// use qtty_core::volume::{CubeOf, CubicMeter, CubicMeters};
66/// use qtty_core::area::SquareMeters;
67/// use qtty_core::length::Meters;
68///
69/// let side = Meters::new(3.0);
70/// let face: SquareMeters = side * side;
71/// let vol: CubeOf<_> = face * side;                 // Quantity<Prod<Prod<Meter, Meter>, Meter>>
72/// let named: CubicMeters = vol.to();
73/// assert!((named.value() - 27.0).abs() < 1e-12);
74/// ```
75pub type CubeOf<L> = Quantity<Prod<Prod<L, L>, L>>;
76
77#[cfg(feature = "customary")]
78mod customary;
79#[cfg(feature = "customary")]
80pub use customary::*;
81
82// ─────────────────────────────────────────────────────────────────────────────
83// SI / metric volume units
84// ─────────────────────────────────────────────────────────────────────────────
85
86/// Cubic metre (SI derived unit of volume).
87#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
88#[unit(symbol = "m³", dimension = Volume, ratio = 1.0)]
89pub struct CubicMeter;
90/// A quantity measured in cubic metres.
91pub type CubicMeters = Quantity<CubicMeter>;
92
93/// Cubic kilometre (`1e9 m³`).
94#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
95#[unit(symbol = "km³", dimension = Volume, ratio = 1e9)]
96pub struct CubicKilometer;
97/// A quantity measured in cubic kilometres.
98pub type CubicKilometers = Quantity<CubicKilometer>;
99
100/// Cubic centimetre (`1e-6 m³`).
101#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
102#[unit(symbol = "cm³", dimension = Volume, ratio = 1e-6)]
103pub struct CubicCentimeter;
104/// A quantity measured in cubic centimetres.
105pub type CubicCentimeters = Quantity<CubicCentimeter>;
106
107/// Cubic millimetre (`1e-9 m³`).
108#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
109#[unit(symbol = "mm³", dimension = Volume, ratio = 1e-9)]
110pub struct CubicMillimeter;
111/// A quantity measured in cubic millimetres.
112pub type CubicMillimeters = Quantity<CubicMillimeter>;
113
114// ─────────────────────────────────────────────────────────────────────────────
115// Litre family
116// ─────────────────────────────────────────────────────────────────────────────
117
118/// Litre (`1e-3 m³`, exact).
119#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
120#[unit(symbol = "L", dimension = Volume, ratio = 1e-3)]
121pub struct Liter;
122/// A quantity measured in litres.
123pub type Liters = Quantity<Liter>;
124
125/// Millilitre (`1e-6 m³`, exact).
126#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
127#[unit(symbol = "mL", dimension = Volume, ratio = 1e-6)]
128pub struct Milliliter;
129/// A quantity measured in millilitres.
130pub type Milliliters = Quantity<Milliliter>;
131
132/// Microlitre (`1e-9 m³`, exact).
133#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
134#[unit(symbol = "µL", dimension = Volume, ratio = 1e-9)]
135pub struct Microliter;
136/// A quantity measured in microlitres.
137pub type Microliters = Quantity<Microliter>;
138
139/// Centilitre (`1e-5 m³`, exact).
140#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
141#[unit(symbol = "cL", dimension = Volume, ratio = 1e-5)]
142pub struct Centiliter;
143/// A quantity measured in centilitres.
144pub type Centiliters = Quantity<Centiliter>;
145
146/// Decilitre (`1e-4 m³`, exact).
147#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
148#[unit(symbol = "dL", dimension = Volume, ratio = 1e-4)]
149pub struct Deciliter;
150/// A quantity measured in decilitres.
151pub type Deciliters = Quantity<Deciliter>;
152
153/// Canonical list of all volume units.
154///
155/// Pass a macro identifier as the single argument; it will be invoked with all
156/// volume unit types as its token list. Drives:
157/// - `impl_unit_from_conversions!` — bidirectional `From` impls between all pairs.
158/// - `impl_unit_cross_unit_ops!` — cross-unit `PartialEq`/`PartialOrd` (feature-gated).
159/// - `assert_units_are_builtin!` — compile-time check that every unit is in
160///   `register_builtin_units!` (under `#[cfg(test)]`).
161///
162/// The macro is exported (`#[doc(hidden)]`) so the `qtty` facade can use it
163/// in compile-time consistency checks (`inventory_consistency.rs`).
164///
165/// ```rust,ignore
166/// volume_units!(crate::impl_unit_from_conversions);
167/// ```
168#[macro_export]
169#[doc(hidden)]
170macro_rules! volume_units {
171    ($cb:path) => {
172        $cb!(
173            CubicMeter,
174            CubicKilometer,
175            CubicCentimeter,
176            CubicMillimeter,
177            Liter,
178            Milliliter,
179            Microliter,
180            Centiliter,
181            Deciliter
182        );
183    };
184}
185
186// Generate all bidirectional From implementations between volume units.
187volume_units!(crate::impl_unit_from_conversions);
188
189// Optional cross-unit operator support (`==`, `<`, etc.).
190#[cfg(feature = "cross-unit-ops")]
191volume_units!(crate::impl_unit_cross_unit_ops);
192
193// Compile-time check: every unit in the inventory is registered as BuiltinUnit.
194#[cfg(test)]
195volume_units!(crate::assert_units_are_builtin);
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use approx::assert_abs_diff_eq;
201
202    #[test]
203    fn liter_to_cubic_meter() {
204        let l = Liters::new(1.0);
205        let m: CubicMeters = l.to();
206        assert_abs_diff_eq!(m.value(), 0.001, epsilon = 1e-15);
207    }
208
209    #[test]
210    fn milliliter_to_liter() {
211        let ml = Milliliters::new(1000.0);
212        let l: Liters = ml.to();
213        assert_abs_diff_eq!(l.value(), 1.0, epsilon = 1e-12);
214    }
215
216    #[test]
217    fn cubic_cm_to_ml() {
218        let cc = CubicCentimeters::new(1.0);
219        let ml: Milliliters = cc.to();
220        assert_abs_diff_eq!(ml.value(), 1.0, epsilon = 1e-12);
221    }
222
223    #[test]
224    #[cfg(feature = "customary")]
225    fn us_gallon_to_liter() {
226        let g = UsGallons::new(1.0);
227        let l: Liters = g.to();
228        assert_abs_diff_eq!(l.value(), 3.785_411_784, epsilon = 1e-6);
229    }
230
231    #[test]
232    #[cfg(feature = "customary")]
233    fn cubic_foot_to_liter() {
234        let cf = CubicFeet::new(1.0);
235        let l: Liters = cf.to();
236        assert_abs_diff_eq!(l.value(), 28.316_846_592, epsilon = 1e-6);
237    }
238
239    #[test]
240    fn length_times_area_to_volume() {
241        use crate::area::{SquareMeter, SquareMeters};
242        use crate::length::{Meter, Meters};
243        use crate::Prod;
244
245        let side = Meters::new(3.0);
246        let face: SquareMeters = (side * side).to();
247        let vol_prod: Quantity<Prod<SquareMeter, Meter>> = face * side;
248        let vol: CubicMeters = vol_prod.to();
249        assert_abs_diff_eq!(vol.value(), 27.0, epsilon = 1e-12);
250    }
251
252    #[test]
253    fn cubic_km_to_liter() {
254        let ckm = CubicKilometers::new(1.0);
255        let l: Liters = ckm.to();
256        assert_abs_diff_eq!(l.value(), 1e12, epsilon = 1e3);
257    }
258
259    #[test]
260    fn cubic_mm_to_cubic_cm() {
261        let mm3 = CubicMillimeters::new(1000.0);
262        let cm3: CubicCentimeters = mm3.to();
263        assert_abs_diff_eq!(cm3.value(), 1.0, epsilon = 1e-12);
264    }
265
266    #[test]
267    fn microliter_to_milliliter() {
268        let ul = Microliters::new(1000.0);
269        let ml: Milliliters = ul.to();
270        assert_abs_diff_eq!(ml.value(), 1.0, epsilon = 1e-12);
271    }
272
273    #[test]
274    fn centiliter_to_liter() {
275        let cl = Centiliters::new(100.0);
276        let l: Liters = cl.to();
277        assert_abs_diff_eq!(l.value(), 1.0, epsilon = 1e-12);
278    }
279
280    #[test]
281    fn deciliter_to_liter() {
282        let dl = Deciliters::new(10.0);
283        let l: Liters = dl.to();
284        assert_abs_diff_eq!(l.value(), 1.0, epsilon = 1e-12);
285    }
286
287    #[test]
288    #[cfg(feature = "customary")]
289    fn cubic_inch_to_cubic_cm() {
290        let cin = CubicInches::new(1.0);
291        let cc: CubicCentimeters = cin.to();
292        // 1 in³ = 16.387064 cm³
293        assert_abs_diff_eq!(cc.value(), 16.387_064, epsilon = 1e-4);
294    }
295
296    #[test]
297    #[cfg(feature = "customary")]
298    fn us_fluid_ounce_to_milliliter() {
299        let floz = UsFluidOunces::new(1.0);
300        let ml: Milliliters = floz.to();
301        // 1 US fl oz ≈ 29.5735 mL
302        assert_abs_diff_eq!(ml.value(), 29.573_529_562_5, epsilon = 1e-6);
303    }
304
305    #[test]
306    fn symbols_are_correct() {
307        assert_eq!(CubicMeter::SYMBOL, "m³");
308        assert_eq!(Liter::SYMBOL, "L");
309        assert_eq!(Milliliter::SYMBOL, "mL");
310        #[cfg(feature = "customary")]
311        assert_eq!(UsGallon::SYMBOL, "gal");
312    }
313}