Thanks to kmdreko in the comments. It turns out that it isn't something you can do in Rust, however it was enough to constraint a single parameter type to the traits I needed. Now my quantity module has no actual mention of Quantity<D,U,V> while still operating on them.
Here's what it looks like :
#[derive(Debug)]
pub struct ParsedValue<L>
where
L: FromStr + Debug + DefaultUnit,
{
pub raw: String,
pub parsed: L,
}
impl<L> ParsedValue<L>
where
L: FromStr + Debug + DefaultUnit,
<L as FromStr>::Err: Debug,
{
// Constructor to create a new ParsedValue
pub fn new(raw: &str) -> Result<Self, ParseError<L>> {
if let Some(captures) = HAS_UNIT_RE.captures(raw) {
let _is_reference = captures.get(1).is_some();
let raw = format!(
"{} {}",
captures[2].to_string(),
if let Some(unit) = captures.get(3) {
unit.as_str()
} else {
L::DEFAULT_UNIT
}
);
// Parse the string into L, or handle failure
let parsed = raw
.parse::<L>()
.map_err(|e| ParseError::UnrecognizedQuantity(e))?;
Ok(ParsedValue {
raw: raw.to_string(),
parsed,
})
} else {
Err(ParseError::InvalidQuantityFormat(raw.to_string()))
}
}
}
use uom::si::f64 as si;
pub trait DefaultUnit {
const DEFAULT_UNIT: &str;
}
/// Length (default: kilometers, since distances in geoscience are often measured in km)
pub type Length = ParsedValue<si::Length>;
impl DefaultUnit for si::Length {
const DEFAULT_UNIT: &str = "km";
}
#[derive(Debug)]
pub enum ParseError<T> where T: FromStr, <T as FromStr>::Err: Debug {
InvalidQuantityFormat(String),
UnrecognizedQuantity(<T as FromStr>::Err)
}
use ParseError::*;
impl<T> Display for ParseError<T> where T: FromStr, <T as FromStr>::Err: Debug {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
InvalidQuantityFormat(s) => format!("Invalid Quantity Format : {}", s),
UnrecognizedQuantity(s) => format!("Unrecognized quantity : '{s:?}'. Check the unit and value.")
}
)
}
}
impl<T> std::error::Error for ParseError<T> where T: FromStr+Debug, <T as FromStr>::Err: Debug {
}