【uom】 2 quantity! 宏解析(quantity.rs)
源码
quantity! 宏是 uom (Units of Measurement) 库的核心组件,用于在 Rust 中实现类型安全的物理量计算和单位转换系统。该宏通过编译时类型检查确保单位计算的正确性,具有零运行时开销的特性。
/// Macro to implement a [quantity][quantity] and associated [measurement units][measurement]. Note
/// that this macro must be executed in direct submodules of the module where the [`system!`] macro
/// was executed. `@...` match arms are considered private.
///
/// * `$quantity_attr`: Quantity attributes. Generally used to set documentation comments for the
/// quantity.
/// * `$quantity`: Quantity name (e.g. `Length`).
/// * `$description`: Quantity description (e.g. `"length"`).
/// * `$dim_attr`: Dimension attributes. Generally used to set documentation comments for the
/// quantity's dimension type alias.
/// * `$system`: System of quantities type (e.g. `ISQ`).
/// * `$dimension`: Power of a factor for each base quantity in the system. Power should be
/// represented as a `typenum` type-level integer (e.g. `N1`, `Z0`, `P1`, `P2`, ...).
/// * `$kind`: [Kind][kind] of the quantity. Optional. This variable should only be specified when
/// defining a quantity that has the same dimensions as another quantity but isn't comparable.
/// When not specified [`crate::Kind`] is used.
/// * `$unit`: Unit name (e.g. `meter`, `foot`).
/// * `$conversion`: Conversion (coefficient and constant factor) from the unit to the base unit of
/// the quantity (e.g. `3.048_E-1` to convert `foot` to `meter`. `1.0_E0, 273.15_E0` to convert
/// `celsius` to `kelvin`.). The coefficient is required and the constant factor is optional.
/// Note that using a unit with a non-zero constant factor is not currently supported as a base
/// unit.
/// * `$abbreviation`: Unit abbreviation (e.g. `"m"`).
/// * `$singular`: Singular unit description (e.g. `"meter"`).
/// * `$plural`: Plural unit description (e.g. `"meters"`).
///
/// An example invocation is given below for the quantity of length in a meter-kilogram-second
/// system. The `#[macro_use]` attribute must be used when including the `uom` crate to make the
/// `quantity!` macro available.
///
/// ```
/// #[macro_use]
/// extern crate uom;
///
/// # fn main() { }
/// # mod mks {
/// #[macro_use]
/// mod length {
/// quantity! {
/// /// Length (base unit meter, m).
/// quantity: Length; "length";
/// /// Length dimension, m.
/// dimension: Q<P1 /*length*/, Z0 /*mass*/, Z0 /*time*/>;
/// units {
/// @meter: 1.0E0; "m", "meter", "meters";
/// @foot: 3.048E-1; "ft", "foot", "feet";
/// }
/// }
/// }
/// # #[macro_use]
/// # mod mass {
/// # quantity! {
/// # /// Mass (base unit kilogram, kg).
/// # quantity: Mass; "mass";
/// # /// Mass dimension, kg.
/// # dimension: Q<Z0 /*length*/, P1 /*mass*/, Z0 /*time*/>;
/// # units {
/// # @kilogram: 1.0; "kg", "kilogram", "kilograms";
/// # }
/// # }
/// # }
/// # #[macro_use]
/// # mod time {
/// # quantity! {
/// # /// Time (base unit second, s).
/// # quantity: Time; "time";
/// # /// Time dimension, s.
/// # dimension: Q<Z0 /*length*/, Z0 /*mass*/, P1 /*time*/>;
/// # units {
/// # @second: 1.0; "s", "second", "seconds";
/// # }
/// # }
/// # }
/// # system! {
/// # /// System of quantities, Q.
/// # quantities: Q {
/// # length: meter, L;
/// # mass: kilogram, M;
/// # time: second, T;
/// # }
/// # /// System of units, U.
/// # units: U {
/// # mod length::Length,
/// # mod mass::Mass,
/// # mod time::Time,
/// # }
/// # }
/// # mod f32 {
/// # Q!(crate::mks, f32/*, (centimeter, gram, second)*/);
/// # }
/// # }
/// ```
///
/// [quantity]: https://jcgm.bipm.org/vim/en/1.1.html
/// [measurement]: https://jcgm.bipm.org/vim/en/1.9.html
/// [kind]: https://jcgm.bipm.org/vim/en/1.2.html
#[macro_export]
macro_rules! quantity {($(#[$quantity_attr:meta])* quantity: $quantity:ident; $description:expr;$(#[$dim_attr:meta])* dimension: $system:ident<$($dimension:ident),+>;$(kind: $kind:ty;)?units {$($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+; $abbreviation:expr,$singular:expr, $plural:expr;)+}) => {mod __system {pub use super::super::*;}$(#[$dim_attr])*pub type Dimension = __system::$system<$($crate::typenum::$dimension),+,quantity!(@kind $($kind)?)>;$(#[$quantity_attr])*////// ## Generic Parameters/// * `U`: Base units./// * `V`: Underlying storage type.pub type $quantity<U, V> = __system::Quantity<Dimension, U, V>;/// Marker trait to identify measurement units for the quantity. See/// [`Unit`](__system::Unit).pub trait Unit: __system::Unit {}/// Trait to identify [units][units] which have a [conversion factor][factor] for the/// `Quantity`. See [`crate::Conversion<V>`].////// ## Generic Parameters/// * `V`: Underlying storage type trait is implemented for.////// [units]: https://jcgm.bipm.org/vim/en/1.13.html/// [factor]: https://jcgm.bipm.org/vim/en/1.24.html#[allow(dead_code)]pub trait Conversion<V>: Unit + $crate::Conversion<V, T = <V as $crate::Conversion<V>>::T>whereV: $crate::Conversion<V>,{/// Check unit validity to ensure the unit is valid for the underlying storage type.#[cfg(test)]fn is_valid() -> bool;}unit! {@units $($(#[$unit_attr])* @$unit: $($conversion),+;$abbreviation, $singular, $plural;)+}/// Quantity description.#[must_use = "method returns a static value"]#[allow(dead_code)]#[inline(always)]pub const fn description() -> &'static str {$description}/// Unit enum.#[allow(non_camel_case_types)]#[non_exhaustive]#[allow(clippy::manual_non_exhaustive)]#[derive(Debug, Clone, Copy)]pub enum Units {$(#[allow(clippy::empty_docs)] // macros cannot expand to enum variants#[doc=$plural]$unit($unit),)+}impl Units {/// Unit abbreviation.#[must_use = "method returns a static value"]#[allow(dead_code)]pub fn abbreviation(&self) -> &'static str {match self {$(Units::$unit(_) => <$unit as __system::Unit>::abbreviation(),)+}}/// Unit singular description.#[must_use = "method returns a static value"]#[allow(dead_code)]pub fn singular(&self) -> &'static str {match self {$(Units::$unit(_) => <$unit as __system::Unit>::singular(),)+}}/// Unit plural description.#[must_use = "method returns a static value"]#[allow(dead_code)]pub fn plural(&self) -> &'static str {match self {$(Units::$unit(_) => <$unit as __system::Unit>::plural(),)+}}}static ALL_UNITS: &[Units] = &[$(Units::$unit($unit),)+];/// Iterate over all defined units for this quantity.#[allow(dead_code)]pub fn units() -> impl Iterator<Item = Units> {ALL_UNITS.iter().copied()}impl<U, V> $quantity<U, V>whereU: __system::Units<V> + ?Sized,V: $crate::num::Num + $crate::Conversion<V>,{/// Create a new quantity from the given value and measurement unit.////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method produces a new value"]#[inline(always)]pub fn new<N>(v: V) -> SelfwhereN: Unit + $crate::Conversion<V, T = V::T>,{$quantity {dimension: $crate::lib::marker::PhantomData,units: $crate::lib::marker::PhantomData,value: __system::to_base::<Dimension, U, V, N>(&v),}}/// Retrieve the value of the quantity in the given measurement unit.////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method returns a new number and does not mutate the original value"]#[inline(always)]pub fn get<N>(&self) -> VwhereN: Unit + $crate::Conversion<V, T = V::T>,{__system::from_base::<Dimension, U, V, N>(&self.value)}/// Returns the largest integer less than or equal to a number in the given/// measurement unit.////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method returns a new number and does not mutate the original value"]#[inline(always)]pub fn floor<N>(self) -> SelfwhereV: $crate::num::Float,N: Unit + $crate::Conversion<V, T = V::T>,{Self::new::<N>(self.get::<N>().floor())}/// Returns the smallest integer less than or equal to a number in the given/// measurement unit.////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method returns a new number and does not mutate the original value"]#[inline(always)]pub fn ceil<N>(self) -> SelfwhereV: $crate::num::Float,N: Unit + $crate::Conversion<V, T = V::T>,{Self::new::<N>(self.get::<N>().ceil())}/// Returns the nearest integer to a number in the in given measurement unit./// Round half-way cases away from 0.0.////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method returns a new number and does not mutate the original value"]#[inline(always)]pub fn round<N>(self) -> SelfwhereV: $crate::num::Float,N: Unit + $crate::Conversion<V, T = V::T>,{Self::new::<N>(self.get::<N>().round())}/// Returns the integer part of a number in the given measurement unit.////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method returns a new number and does not mutate the original value"]#[inline(always)]pub fn trunc<N>(self) -> SelfwhereV: $crate::num::Float,N: Unit + $crate::Conversion<V, T = V::T>,{Self::new::<N>(self.get::<N>().trunc())}/// Returns the fractional part of a number in the given measurement unit.////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method returns a new number and does not mutate the original value"]#[inline(always)]pub fn fract<N>(self) -> SelfwhereV: $crate::num::Float,N: Unit + $crate::Conversion<V, T = V::T>,{Self::new::<N>(self.get::<N>().fract())}/// Creates a struct that can be used to format a compatible quantity for display.////// # Notes/// The return value of this method cannot be used to print directly, but is instead/// used to format quantities and can be reused; see/// [`Arguments::with`](super::fmt::Arguments::with()) and the examples below.////// If you do not need to format multiple quantities, consider using/// [`into_format_args`](#method.into_format_args) instead.////// # Examples#[cfg_attr(all(feature = "si", feature = "f32"), doc = " ```rust")]#[cfg_attr(not(all(feature = "si", feature = "f32")), doc = " ```rust,ignore")]/// # use uom::si::f32::*;/// # use uom::si::time::{femtosecond, picosecond};/// # use uom::si::fmt::Arguments;/// # use uom::fmt::DisplayStyle::*;/// let t1 = Time::new::<femtosecond>(1.0_E-1);/// let t2 = Time::new::<picosecond>(1.0_E-1);/// let a = Time::format_args(femtosecond, Description);////// assert_eq!("0.1 femtoseconds", format!("{}", a.with(t1)));/// assert_eq!("100 femtoseconds", format!("{}", a.with(t2)));/// ```////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method returns a new object"]pub fn format_args<N>(_unit: N,style: $crate::fmt::DisplayStyle) -> __system::fmt::Arguments<Dimension, N>whereN: Unit{__system::fmt::Arguments {dimension: $crate::lib::marker::PhantomData,unit: $crate::lib::marker::PhantomData,style,}}/// Creates a struct that formats `self` for display.////// # Notes/// Unlike [`format_args`](#method.format_args), the return value of this method can be/// used directly for display. It will format the value of `self` for the quantity on/// which it is called and nothing else.////// If you wish to reuse the return value to format multiple quantities, use/// [`format_args`](#method.format_args) instead.////// # Examples#[cfg_attr(all(feature = "si", feature = "f32"), doc = " ```rust")]#[cfg_attr(not(all(feature = "si", feature = "f32")), doc = " ```rust,ignore")]/// # use uom::si::f32::*;/// # use uom::si::time::{femtosecond, picosecond};/// # use uom::si::fmt::Arguments;/// # use uom::fmt::DisplayStyle::*;/// let t = Time::new::<picosecond>(1.0_E-1);/// let a = t.into_format_args(femtosecond, Description);////// assert_eq!("100 femtoseconds", format!("{}", a));/// ```////// ## Generic Parameters/// * `N`: Unit.#[must_use = "method returns a new object and does not mutate the original one"]pub fn into_format_args<N>(self,_unit: N,style: $crate::fmt::DisplayStyle) -> __system::fmt::QuantityArguments<Dimension, U, V, N>whereN: Unit{__system::fmt::QuantityArguments {arguments: __system::fmt::Arguments {dimension: $crate::lib::marker::PhantomData,unit: $crate::lib::marker::PhantomData,style,},quantity: self,}}}impl<N> __system::fmt::Arguments<Dimension, N>whereN: __system::Unit + Unit,{/// Specifies a quantity to display.////// ## Generic Parameters/// * `U`: Base units./// * `V`: Underlying storage type trait is implemented for.#[must_use = "method returns a new object and does not mutate the original one"]pub fn with<U, V>(self,quantity: $quantity<U, V>) -> __system::fmt::QuantityArguments<Dimension, U, V, N>whereU: __system::Units<V> + ?Sized,V: $crate::num::Num + $crate::Conversion<V>,{__system::fmt::QuantityArguments {arguments: self,quantity,}}}mod str {storage_types! {use $crate::lib::str::FromStr;use $crate::str::ParseQuantityError::*;impl<U> FromStr for super::super::$quantity<U, V>whereU: super::super::__system::Units<V> + ?Sized,{type Err = $crate::str::ParseQuantityError;fn from_str(s: &str) -> Result<Self, Self::Err> {let mut parts = s.splitn(2, ' ');let value = parts.next().unwrap();let unit = parts.next().ok_or(NoSeparator)?;let value = value.parse::<V>().map_err(|_| ValueParseError)?;#[allow(unreachable_patterns)]match unit.trim() {$($abbreviation | $singular | $plural => Ok(Self::new::<super::super::$unit>(value)),)+_ => Err(UnknownUnit),}}}}}};(@kind $kind:ty) => { $kind };(@kind) => { dyn $crate::Kind };
}
二、宏参数详解与架构设计
1. 宏参数完整说明
/// Macro to implement a [quantity][quantity] and associated [measurement units][measurement]...
#[macro_export]
macro_rules! quantity {($(#[$quantity_attr:meta])* quantity: $quantity:ident; $description:expr;$(#[$dim_attr:meta])* dimension: $system:ident<$($dimension:ident),+>;$(kind: $kind:ty;)?units {$($(#[$unit_attr:meta])* @$unit:ident: $($conversion:expr),+; $abbreviation:expr, $singular:expr, $plural:expr;)+}) => { /* 宏展开内容 */ };(@kind $kind:ty) => { $kind };(@kind) => { dyn $crate::Kind };
}
参数说明补充:
-
$unit_attr: 单位元属性,用于单位级别的文档注释
-
转换系数支持科学计数法表示(如3.048E-1)
-
支持温度等需要常数因子的特殊转换(如1.0_E0, 273.15_E0)
2. 核心架构实现
维度系统增强实现
pub type Dimension = __system::$system<$($crate::typenum::$dimension),+,quantity!(@kind $($kind)?)>;
改进点:
-
增加了对物理量种类(Kind)的支持
-
使用typenum的类型级整数进行维度计算
-
支持7个基本量的任意幂次组合(P1表示正幂,Z0表示零幂,N1表示负幂)
物理量类型扩展
pub type $quantity<U, V> = __system::Quantity<Dimension, U, V>;
泛型参数说明:
-
U: 单位类型,实现Units trait
-
V: 数值类型,需实现num::Num和Conversion trait
-
使用PhantomData标记维度信息,零运行时开销
三、关键功能深度实现
1. 单位系统实现细节
unit! {@units $($(#[$unit_attr])* @$unit: $($conversion),+;$abbreviation, $singular, $plural;)+
}
内部生成:
-
为每个单位生成独立的结构体
-
实现Unit和Conversion trait
-
包含转换系数和常数因子
-
自动生成单位枚举Units和迭代器
2. 数学运算完整实现
pub fn floor<N>(self) -> Self
whereV: $crate::num::Float,N: Unit + $crate::Conversion<V, T = V::T>,
{Self::new::<N>(self.get::<N>().floor())
}
特点:
-
保持单位一致性的舍入运算
-
支持所有浮点数运算方法
-
编译时单位类型检查
3. 格式化系统实现
pub fn into_format_args<N>(self,_unit: N,style: $crate::fmt::DisplayStyle
) -> __system::fmt::QuantityArguments<Dimension, U, V, N>
{/* 实现细节 */
}
格式化策略:
-
支持缩写、单数和复数形式
-
可复用的格式化器设计
-
显示样式控制(Description, Abbreviation)
四、类型安全机制详解
1. 维度检查实现
impl<U, V> Add for $quantity<U, V>
whereU: Units<V>,V: Num + Conversion<V>,
{type Output = Self;fn add(self, rhs: Self) -> Self::Output {Self {dimension: PhantomData,units: PhantomData,value: self.value + rhs.value,}}
}
安全保证:
-
编译时检查操作数维度匹配
-
非法运算直接导致编译错误
-
自动单位系统转换
2. 单位转换实现
pub fn new<N>(v: V) -> Self
whereN: Unit + $crate::Conversion<V, T = V::T>,
{$quantity {dimension: PhantomData,units: PhantomData,value: __system::to_base::<Dimension, U, V, N>(&v),}
}
转换过程:
-
输入值按指定单位解释
-
转换为基本单位存储
-
转换系数编译期计算
-
常数因子运行时处理
五、扩展性与最佳实践
1. 自定义单位实现示例
quantity! {/// 自定义压强单位quantity: MyPressure; "custom pressure";dimension: ISQ<N1, P1, N2>; // L^-1 M^1 T^-2units {@my_unit: 133.322; "mu", "my unit", "my units";@standard_unit: 101325.0; "std", "standard", "standards";}
}
2. 错误处理最佳实践
impl<U> FromStr for $quantity<U, V> {type Err = ParseQuantityError;fn from_str(s: &str) -> Result<Self, Self::Err> {// 严格解析逻辑}
}
错误类型:
-
NoSeparator: 缺少值单位分隔符
-
ValueParseError: 数值解析失败
-
UnknownUnit: 未知单位名称
六、性能优化策略
-
编译期计算:所有单位转换系数在编译期确定
-
零成本抽象:运行时无动态分派或类型擦除
-
内联优化:关键方法标记为#[inline(always)]
-
格式化复用:重用格式化器减少开销
#[inline(always)]
pub fn get<N>(&self) -> V {__system::from_base::<Dimension, U, V, N>(&self.value)
}
七、完整应用案例
#[macro_use]
mod thermodynamics {quantity! {/// 温度(基本单位:开尔文)quantity: Temperature; "temperature";dimension: ISQ<Z0, Z0, P0, Z0, Z0, Z0, Z0>; // 基本量均为零次幂units {@kelvin: 1.0; "K", "kelvin", "kelvins";@celsius: 1.0, 273.15; "°C", "degree Celsius", "degrees Celsius";@fahrenheit: 5.0/9.0, 459.67*(5.0/9.0); "°F", "degree Fahrenheit", "degrees Fahrenheit";}}
}// 使用示例
let boiling = Temperature::new::<celsius>(100.0);
let freezing = Temperature::new::<fahrenheit>(32.0);
let delta = boiling - freezing; // 自动转换为开尔文计算
八、实现原理深度解析
1. 类型级编程体系
pub type Dimension = __system::$system<$($crate::typenum::$dimension),+,quantity!(@kind $($kind)?)
>;
关键技术:
-
使用typenum进行维度计算
-
类型级整数表示幂次
-
PhantomData标记单位/维度
2. trait系统设计
pub trait Conversion<V>: Unit + $crate::Conversion<V, T = <V as $crate::Conversion<V>>::T>
whereV: $crate::Conversion<V>,
{#[cfg(test)]fn is_valid() -> bool;
}
关键trait:
-
Unit: 标记有效单位
-
Conversion: 处理单位转换
-
Units: 单位系统约束
九、总结
quantity! 宏通过精妙的类型系统设计,在保持Rust零成本抽象优势的同时,提供了强大的单位安全保证。其主要特点包括:
-
完全的类型安全:编译时检查所有单位运算
-
零运行时开销:完全基于编译期计算
-
灵活的扩展性:支持自定义单位和物理量
-
丰富的功能:数学运算、格式化、解析等
-
工程友好:完善的文档和错误处理
该宏特别适合科学计算、工程仿真、金融分析等需要严格单位控制的领域,是Rust生态中物理量处理的标杆实现。