Expand description
This library implements a Money datatype that supports both a
statically-typed and dynamically-typed Currency. That is to say,
you can create a Money<USD> that is a totally different type than
a Money<JPY>, or you can create a Money<&dyn Currency> where
the currency is determined at runtime, but still safely do math with
it (i.e., Money<&dyn Currency> + Money<&dyn Currency> returns a
fallible Result because the currencies might be different).
For example:
use rust_decimal::Decimal;
use doubloon::{
{Money, Currency, MoneyMathError},
iso_currencies::{USD, JPY, EUR},
currency_map::CurrencyMap,
};
// Instances with statically-typed currencies.
let m_usd = Money::new(1, USD);
let m_jpy = Money::new(1, JPY);
// This won't even compile because they are two different types.
// let no_compile = m_usd + m_jpy;
// But you can add same currencies together.
assert_eq!(
m_usd + m_usd,
Money::new(2, USD)
);
// If you don't know the currency until runtime, just use a
// dynamically-typed Currency (&dyn Currency).
let currency_map = CurrencyMap::from_collection(vec![&USD as &dyn Currency, &JPY]);
let m_dyn_usd = Money::new(1, currency_map.get("USD").unwrap());
let m_dyn_jpy = Money::new(1, currency_map.get("JPY").unwrap());
// Adding same currencies produces an Ok Result.
assert_eq!(
m_dyn_usd + m_dyn_usd,
Ok(Money::new(2, currency_map.get("USD").unwrap()))
);
// Adding different currencies produces an Err Result.
assert_eq!(
m_dyn_usd + m_dyn_jpy,
Err(MoneyMathError::IncompatibleCurrencies("USD", "JPY"))
);
// Locale-aware formatting is provided via the icu crate
// when the "formatting" feature of this crate is enabled.
use icu::locale::locale;
let m = Money::new(Decimal::new(123456789, 2), EUR);
// en-US uses comma for group separator, period for decimal separator,
// with the symbol at the left with no spacing.
assert_eq!(m.format(&locale!("en-US")), "€1,234,567.89");
// ir-IR is like en-US except there is a narrow non-breaking space
// between the symbol and the amount.
assert_eq!(m.format(&locale!("ir-IR")), "€\u{a0}1,234,567.89");
// tr-TR is similar to en-US but uses period for the group separator
// and comma for the decimal separator.
assert_eq!(m.format(&locale!("tr-TR")), "€1.234.567,89");
// fr-FR puts the symbol at the end, and uses non-breaking spaces
// between digit groups, comma as a decimal separator,
// and a narrow non-breaking space between the amount and symbol.
assert_eq!(
m.format(&locale!("fr-FR")),
"1\u{202f}234\u{202f}567,89\u{a0}€"
);
§Installation and Features
cargo add doubloonTo enable formatting and/or serde support, enable the “formatting” and/or “serde” features.
cargo add doubloon --features "serde,formatting"The serde feature enables serialization to a struct with separate fields for the amount and currency, suitable for storing in a database or sending to another service or client. The amount is serialized as a string to preserve precision.
Because applications can define their own Currency
implementations, there’s no global map one can use
to deserialize a currency code back into a Currency
instance, so deserialization is a two-step process.
First deserialize into a struct with a Decimal and
String field. Then use the currency code string
to resolve and construct the appropriate Currency
instance, and pass that as well as the Decimal to
Money::new().
§Changes from Previous Versions
§Version 2.0.0 -> 3.0.0
- The
round()method now rounds the amount to the currency’s number of minor units by default. Use the newround_to_precision()to round the amount to some other precision. - New
to_minor_units()method that returns the amount in currency minor units, suitable for sending to a payment processor.
§Version 1.0.0 -> 2.0.0
- Multiplication, division, and remainder operations now
take a numeric argument instead of another
Moneyinstance. This makes more intuitive sense: one typically multiplies a price against a quantity, not another price, to get an extended price. - Support for the
Powtrait has been removed. I originally included this because it was supported by the underlyingDecimal, but after thinking about it more, I realized it doesn’t really make sense to raise a monetary amount to a power. There aren’t “dollars squared” like there are “meters squared.” Money::new()now accepts anything that implementsInto<Decimal>for the amount, including integer literals.
§Version 0.2.0 -> 1.0.0
- Formatting is now locale-aware, thanks to the
icucrate. The.format()method now requires an icuLocale, which will affect how the monetary amount will be formatted. Since the icu crate is rather chunky, formatting is now an optional feature.
Modules§
- currency_
map - Provides a simplified currency code to
&dyn Currencylookup map. - formatting
- iso_
currencies - ISO 4217 Currency definitions, published on 2026-01-01
Structs§
- Money
- An amount of money in a particular currency.
Enums§
- Money
Math Error - Errors that can occur when doing math with Money instances that have dynamically-typed currencies
- Rounding
Strategy - Strategies for use with the Money::round method.
RoundingStrategyrepresents the different rounding strategies that can be used byround_dp_with_strategy.
Traits§
- Currency
- Common trait for all currencies.
- Minor
Units - Used as a trait bound when constructing new instances of Money from minor units.