uom
uom copied to clipboard
Make methods on Quantity more generic
Currently, the Quantity struct has implemented a few methods like new
and format_args
defined on (what looks like) the given dimensions for that system of measurements
Sample methods defined on the struct
I am writing a struct generic over any quantity in the SI system of units:
The generic struct
pub type Quantity<T> = uom::si::Quantity<T, SI<f64>, f64>;
pub struct Vector3<T: Dimension + ?Sized> {
x: Quantity<T>,
y: Quantity<T>,
z: Quantity<T>,
}
impl<T: Dimension + ?Sized> Debug for Vector3<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Vector3")
.field("x", &Quantity::format_args(self.x, Abbreviation)) // Same error over T:
.field("y", &Quantity::format_args(self.xy Abbreviation))
.field("z", &Quantity::format_args(self.z, Abbreviation))
.finish()
}
}
impl<T: Dimension + ?Sized> Vector3<T> {
pub fn new(x: Quantity<T>, y: Quantity<T>, z: Quantity<T>) -> Self {
Self { x, y, z }
}
pub fn zero() -> Self {
Self {
x: Quantity::new(), // Or T::new(), neither work
y: Quantity::new(),
z: Quantity::new(),
}
}
}
but am running into problems since the new
and format_args
methods are only defined on the specific Dimension
types that were (maybe) defined in the macro invocation which makes it almost impossible to use the Quantity type in a generic context.
The errors
A solution I had that I wanted to propose before attempting would be to make a trait that is implemented for each dimension versus new methods implemented directly on the struct so that the Quantity
as long as it implements the trait. Something like:
trait SIQuantity {
// Here new and all those methods would be layed out
}
impl SIQuantity for Quantity<specific_dimension, ....> {
// Per dimention implementation
}
which would allow the above code to become:
mpl<T: Dimension + ?Sized> Debug for Vector3<T>
where
Quantity<T>: SIQuantity,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Vector3")
.field("x", &Quantity::format_args(self.x, Abbreviation))
.field("y", &Quantity::format_args(self.xy Abbreviation))
.field("z", &Quantity::format_args(self.z, Abbreviation))
.finish()
}
}
I have skimmed through the source code and tried to implement it myself, but I am not familiar enough with the code base to make anything useful.
I might also just be using the crate wrong
This is definitely a point that isn't ideal and I haven't put enough effort into finding a great solution yet. Currently Quantity
's fields are public so you can use the type constructor:
// Gravitational constant.
const G: Quantity<uom::si::ISQ<P3, N1, N2, Z0, Z0, Z0, Z0>, uom::si::SI<f64>, f64> = Quantity { dimension: PhantomData, units: PhantomData, value: 6.674e-11, };
// Based on your generic example.
let x = Quantity<T, uom::si::SI<f64>, f64> = Quantity { dimension: PhantomData, units: PhantomData, value: 6.674e-11, };
Edit: leaving this open rather than re-opening #28. I like to make the fields internal eventually and have a better solution that doesn't rely on exposing the struct details.
Yah, thats what I have started to do. Thanks for all your work on it its such a great crate.
I have a minimal use case to illustrate the need to use methods defined on Quantity in a generic way.
Let's say that we want to make a generic formatter for some application that can format a quantity and validate user input. The following is a non-generic example using specifically Pressure
as quantity type and megapascal
for formatting. Currently, it seems that traits are not shared between types of quantities or units, so it is not possible to turn Pressure
or megapascal
into generic parameters.
use uom::fmt::DisplayStyle::Abbreviation;
use uom::si::f64::Pressure;
use uom::si::pressure::{kilopascal, megapascal};
use uom::str::ParseQuantityError;
struct UnitFormatter;
impl UnitFormatter {
fn validate(&self, input: &str)
-> std::result::Result<Pressure, ParseQuantityError> {
input.parse::<Pressure>()
}
fn format(&self, value: &Pressure) -> String {
format!("{}", value.into_format_args(megapascal, Abbreviation))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unit_formatter() {
let v = UnitFormatter { };
let p = Pressure::new::<kilopascal>(1000.0);
assert_eq!(v.format(&p), "1 MPa");
assert_eq!(v.validate("1 MPa").unwrap(), p);
}
}