Skip to main content

Crate doubloon

Crate doubloon 

Source
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 doubloon

To 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 new round_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 Money instance. This makes more intuitive sense: one typically multiplies a price against a quantity, not another price, to get an extended price.
  • Support for the Pow trait has been removed. I originally included this because it was supported by the underlying Decimal, 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 implements Into<Decimal> for the amount, including integer literals.

§Version 0.2.0 -> 1.0.0

  • Formatting is now locale-aware, thanks to the icu crate. The .format() method now requires an icu Locale, 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 Currency lookup map.
formatting
iso_currencies
ISO 4217 Currency definitions, published on 2026-01-01

Structs§

Money
An amount of money in a particular currency.

Enums§

MoneyMathError
Errors that can occur when doing math with Money instances that have dynamically-typed currencies
RoundingStrategy
Strategies for use with the Money::round method. RoundingStrategy represents the different rounding strategies that can be used by round_dp_with_strategy.

Traits§

Currency
Common trait for all currencies.
MinorUnits
Used as a trait bound when constructing new instances of Money from minor units.