uom icon indicating copy to clipboard operation
uom copied to clipboard

Simpler way to extract raw value and lack of dynamic units.

Open chyvonomys opened this issue 2 years ago • 2 comments

Hi, I'm trying to use this crate as a registry of units and conversions. I have a list of values (e.g pressure at different points) in one units (e.g kPa), and I want to convert all of them to other units known at runtime, user enters them.

So far I have figured out how to do it in a limited way for one value:

use uom::si::*;


fn convert_pressure<N: pressure::Unit + uom::Conversion<f32, T=f32>>(src: f32::Pressure, dst_unit: N) -> f32 {
    let a = f32::Pressure::format_args(dst_unit, uom::fmt::DisplayStyle::Abbreviation);
    let s = format!("{}", a.with(src));
    s.split_whitespace().next().unwrap().parse::<f32>().unwrap()
}

fn main() {
    let src = f32::Pressure::new::<pressure::atmosphere>(2.0f32);
    dbg!(src);

    for dst_abbrev in ["kPa", "mm Hg", "in Hg", "atm", "psi", "km"] {
        dbg!(dst_abbrev);

        let units: Option<pressure::Units> = pressure::units().find(|x| x.abbreviation() == dst_abbrev);
        if let Some(units) = units {
            let dst_val = match units {
                pressure::Units::kilopascal(dst_unit) => {
                    convert_pressure(src, dst_unit)
                },
                pressure::Units::millimeter_of_mercury(dst_unit) => {
                    convert_pressure(src, dst_unit)
                },
                pressure::Units::inch_of_mercury(dst_unit) => {
                    convert_pressure(src, dst_unit)
                },
                pressure::Units::atmosphere(dst_unit) => {
                    convert_pressure(src, dst_unit)
                },
                _ => {println!("`{}` is not supported", dst_abbrev); continue},
            };
            dbg!(dst_val);
        } else {
            println!(
                "{} is not a pressure unit, use one of {:?}", dst_abbrev,
                pressure::units().map(|x| x.abbreviation()).collect::<Vec<_>>(),
            );
            continue
        }
    }
}
  1. There is no way to extract value after conversion, I'm doing format_args into string, and then parse first field from that string. This is very inefficient workaround. I looked in sources and there is internal from_base function that is exactly what I need in fact. But it is not exposed to users.
  2. Runtime abbreviation -> Unit is impossible, I can look up the enum value of pressure::Units, but all other functions accept only concrete type, there is no way to pass Unit and dynamically dispatch. So at the moment I can only manually pattern match dst_unit, and no way to cover all enum variants automatically.

This lack of dynamic units seems to be by design, but looks like it may be useful in cases like this. Maybe enabled by some feature gate.

chyvonomys avatar Nov 27 '21 20:11 chyvonomys

Related to #219, #91, #220

chyvonomys avatar Nov 27 '21 20:11 chyvonomys

I kind of improved Units switch and conversion to be in one place and executed once:

use uom::si::*;

fn main() {
    let src = [1.0f32, 2.0, 10.0]; // in atm

    for dst_abbrev in ["kPa", "mm Hg", "in Hg", "atm", "psi", "km"] {
        let units: Option<pressure::Units> = pressure::units().find(|x| x.abbreviation() == dst_abbrev);
        if let Some(units) = units {
            let conv: Option<fn(f32::Pressure) -> f32> = match units {
                pressure::Units::kilopascal(_) => Some(|src| src.get::<pressure::kilopascal>()),
                pressure::Units::millimeter_of_mercury(_) => Some(|src| src.get::<pressure::millimeter_of_mercury>()),
                pressure::Units::inch_of_mercury(_) => Some(|src| src.get::<pressure::inch_of_mercury>()),
                pressure::Units::atmosphere(_) => Some(|src| src.get::<pressure::atmosphere>()),
                _ => None,
            };
            if let Some(conv) = conv {
                for s in src {
                    let d = conv(f32::Pressure::new::<pressure::atmosphere>(s));
                    println!("{} {} -> {} {}", s, pressure::atmosphere::abbreviation(), d, dst_abbrev);
                }
            } else {
                println!("`{}` is not supported", dst_abbrev);
                continue
            }
        } else {
            println!(
                "{} is not a pressure unit, use one of {:?}", dst_abbrev,
                pressure::units().map(|x| x.abbreviation()).collect::<Vec<_>>(),
            );
            continue
        }
    }
}

I'm thinking maybe this can be implemented as macro of an exhaustive match of all Units variants or something..

chyvonomys avatar Nov 28 '21 01:11 chyvonomys