pub struct ExchangeRate { /* private fields */ }Expand description
The rate at which one currency buys another.
A rate works in one direction only: the rate from dollars to euros is not
the rate back. It multiplies, so it is always positive. The currency being
priced is the base, and the currency it is priced
in is the quote. Two rates that share a currency
combine with cross_with, which is how a pair
nobody prices directly gets priced.
§Example
use lucre::{Currency, ExchangeRate, Money};
use rust_decimal::dec;
let rate = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(
rate.convert(Money::from_major(100, Currency::USD))?,
Money::from_major(90, Currency::EUR)
);Implementations§
Source§impl ExchangeRate
impl ExchangeRate
Sourcepub fn new<P: Into<Pair>, R: Into<Decimal>>(
pair: P,
rate: R,
) -> Result<Self, ExchangeRateError>
pub fn new<P: Into<Pair>, R: Into<Decimal>>( pair: P, rate: R, ) -> Result<Self, ExchangeRateError>
Quote a rate over a currency pair.
The pair may be given as a Pair or as the two currencies alone,
base first. The rate multiplies an amount in the base currency to give
an amount in the quote currency. A currency may be priced against
itself, though identity says that more
plainly.
§Example
use lucre::{Currency, ExchangeRate, Pair};
use rust_decimal::dec;
let quoted = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(quoted, ExchangeRate::new("USD/EUR".parse::<Pair>()?, dec!(0.9))?);
assert!(ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0)).is_err());§Errors
Returns ExchangeRateError::InvalidRate if the rate is zero or
negative.
Sourcepub fn identity(currency: Currency) -> Self
pub fn identity(currency: Currency) -> Self
The rate of a currency against itself. Converting leaves an amount unchanged.
§Example
use lucre::{Currency, ExchangeRate, Money};
let fare = Money::from_minor(275, Currency::USD);
assert_eq!(ExchangeRate::identity(Currency::USD).convert(fare)?, fare);Sourcepub fn base(self) -> Currency
pub fn base(self) -> Currency
The currency being priced.
§Example
use lucre::{Currency, ExchangeRate};
use rust_decimal::dec;
let rate = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(rate.base(), Currency::USD);Sourcepub fn quote(self) -> Currency
pub fn quote(self) -> Currency
The currency it is priced in.
§Example
use lucre::{Currency, ExchangeRate};
use rust_decimal::dec;
let rate = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(rate.quote(), Currency::EUR);Sourcepub fn pair(self) -> Pair
pub fn pair(self) -> Pair
Both currencies together, base first.
§Example
use lucre::{Currency, ExchangeRate, Pair};
use rust_decimal::dec;
let rate = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(rate.pair(), Pair::new(Currency::USD, Currency::EUR));Sourcepub fn rate(self) -> Decimal
pub fn rate(self) -> Decimal
The multiplier.
§Example
use lucre::{Currency, ExchangeRate};
use rust_decimal::dec;
let rate = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(rate.rate(), dec!(0.9));Sourcepub fn convert(self, money: Money) -> Result<Money, ConvertError>
pub fn convert(self, money: Money) -> Result<Money, ConvertError>
Restate an amount in the quote currency.
The result keeps every digit the multiplication produced. Rounding to
the currency’s minor units is Money::round’s job.
§Example
use lucre::{ConvertError, Currency, ExchangeRate, Money};
use rust_decimal::dec;
let rate = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(
rate.convert(Money::from_minor(2550, Currency::USD))?,
Money::from_decimal(dec!(22.950), Currency::EUR)
);
assert!(matches!(
rate.convert(Money::from_major(10, Currency::GBP)),
Err(ConvertError::CurrencyMismatch {
base: Currency::USD,
found: Currency::GBP,
..
})
));§Errors
Returns ConvertError::CurrencyMismatch if the amount is not in the
base currency.
Returns ConvertError::Overflow if the result is too large for a
Decimal.
Sourcepub fn cross_with(self, other: Self) -> Result<Self, CrossRateError>
pub fn cross_with(self, other: Self) -> Result<Self, CrossRateError>
Combine two rates that share a currency.
This rate’s quote currency must be other’s base currency. The result
is one rate from this rate’s base to other’s quote, which is the
usual way to price a pair quoted only against some third currency.
§Example
use lucre::{Currency, ExchangeRate};
use rust_decimal::dec;
let usd_eur = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
let eur_jpy = ExchangeRate::new((Currency::EUR, Currency::JPY), dec!(160))?;
let usd_jpy = usd_eur.cross_with(eur_jpy)?;
assert_eq!(usd_jpy.base(), Currency::USD);
assert_eq!(usd_jpy.quote(), Currency::JPY);
assert_eq!(usd_jpy.rate(), dec!(144.0));§Errors
Returns CrossRateError::CurrencyMismatch if this rate’s quote
currency is not other’s base currency.
Returns CrossRateError::Overflow if the combined rate is too large
for a Decimal, and CrossRateError::Rate if the combined rate
cannot be quoted.
Sourcepub fn invert(self) -> Result<Self, InvertError>
pub fn invert(self) -> Result<Self, InvertError>
The rate back the other way, over the inverted pair.
The multiplier becomes one divided by this one, so the result prices the quote currency in the base. This treats both directions as priced alike. A desk that charges a spread quotes each direction separately instead.
Dividing keeps 28 significant digits, so inverting twice need not give back the rate it started from.
§Example
use lucre::{Currency, ExchangeRate, Money};
use rust_decimal::dec;
let usd_eur = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.8))?;
let eur_usd = usd_eur.invert()?;
assert_eq!(eur_usd.base(), Currency::EUR);
assert_eq!(eur_usd.quote(), Currency::USD);
assert_eq!(eur_usd.rate(), dec!(1.25));
assert_eq!(
eur_usd.convert(Money::from_major(80, Currency::EUR))?,
Money::from_major(100, Currency::USD)
);§Errors
Returns InvertError::Underflow if the multiplier is 2e28 or
above, since one divided by it rounds away to zero.
Trait Implementations§
Source§impl Clone for ExchangeRate
impl Clone for ExchangeRate
Source§fn clone(&self) -> ExchangeRate
fn clone(&self) -> ExchangeRate
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for ExchangeRate
Source§impl Debug for ExchangeRate
impl Debug for ExchangeRate
Source§impl<'de> Deserialize<'de> for ExchangeRate
Available on crate feature serde only.Reads all three fields, in any order. All three are required, and any other
field is skipped, such as the time of the quote. The rate must pass the same
check as ExchangeRate::new: a rate of zero or less is rejected.
impl<'de> Deserialize<'de> for ExchangeRate
serde only.Reads all three fields, in any order. All three are required, and any other
field is skipped, such as the time of the quote. The rate must pass the same
check as ExchangeRate::new: a rate of zero or less is rejected.
§Example
use lucre::{Currency, ExchangeRate};
use rust_decimal::dec;
let quoted = r#"{"as_of": "2026-08-14", "base": "USD", "quote": "EUR", "rate": 0.9}"#;
assert_eq!(
serde_json::from_str::<ExchangeRate>(quoted)?,
ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?
);
assert!(
serde_json::from_str::<ExchangeRate>(r#"{"base": "USD", "quote": "EUR", "rate": 0}"#)
.is_err()
);Source§fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>
Source§impl Display for ExchangeRate
Writes the pair, a space, and the multiplier. The multiplier keeps the
digits it was quoted with, since a rate has no minor units to pad out to.
impl Display for ExchangeRate
Writes the pair, a space, and the multiplier. The multiplier keeps the digits it was quoted with, since a rate has no minor units to pad out to.
§Example
use lucre::{Currency, ExchangeRate};
use rust_decimal::dec;
let rate = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(rate.to_string(), "USD/EUR 0.9");
// Width, fill, and alignment flags are honored. The default is right
// alignment, as it is for an amount.
assert_eq!(format!("{rate:>13}"), " USD/EUR 0.9");impl Eq for ExchangeRate
Source§impl<'a> Extend<&'a ExchangeRate> for Exchange
impl<'a> Extend<&'a ExchangeRate> for Exchange
Source§fn extend<I: IntoIterator<Item = &'a ExchangeRate>>(&mut self, iter: I)
fn extend<I: IntoIterator<Item = &'a ExchangeRate>>(&mut self, iter: I)
Source§fn extend_one(&mut self, item: A)
fn extend_one(&mut self, item: A)
extend_one)Source§fn extend_reserve(&mut self, additional: usize)
fn extend_reserve(&mut self, additional: usize)
extend_one)Source§impl Extend<ExchangeRate> for Exchange
Adds each rate as set_rate does, so a pair quoted
more than once keeps the rate given last. This holds whether the rate it
displaces came from the same batch or was already in the table.
impl Extend<ExchangeRate> for Exchange
Adds each rate as set_rate does, so a pair quoted
more than once keeps the rate given last. This holds whether the rate it
displaces came from the same batch or was already in the table.
§Example
use lucre::{Currency, Exchange, ExchangeRate};
use rust_decimal::dec;
let mut desk = Exchange::new();
desk.set_rate(ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?);
desk.extend([
ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.92))?,
ExchangeRate::new((Currency::EUR, Currency::JPY), dec!(160))?,
]);
assert_eq!(
desk.rate((Currency::USD, Currency::EUR)).map(|rate| rate.rate()),
Some(dec!(0.92))
);
assert_eq!(desk.iter().len(), 2);Source§fn extend<I: IntoIterator<Item = ExchangeRate>>(&mut self, iter: I)
fn extend<I: IntoIterator<Item = ExchangeRate>>(&mut self, iter: I)
Source§fn extend_one(&mut self, item: A)
fn extend_one(&mut self, item: A)
extend_one)Source§fn extend_reserve(&mut self, additional: usize)
fn extend_reserve(&mut self, additional: usize)
extend_one)Source§impl<'a> FromIterator<&'a ExchangeRate> for Exchange
impl<'a> FromIterator<&'a ExchangeRate> for Exchange
Source§fn from_iter<I: IntoIterator<Item = &'a ExchangeRate>>(iter: I) -> Self
fn from_iter<I: IntoIterator<Item = &'a ExchangeRate>>(iter: I) -> Self
Source§impl FromIterator<ExchangeRate> for Exchange
Collects rates into a table, adding each one as Extend does.
impl FromIterator<ExchangeRate> for Exchange
Collects rates into a table, adding each one as Extend does.
§Example
use lucre::{Currency, Exchange, ExchangeRate, Money};
use rust_decimal::dec;
let feed = [
ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?,
ExchangeRate::new((Currency::EUR, Currency::JPY), dec!(160))?,
];
let desk: Exchange = feed.into_iter().collect();
assert_eq!(
desk.convert(Money::from_major(100, Currency::USD), Currency::EUR)?,
Money::from_major(90, Currency::EUR)
);Source§fn from_iter<I: IntoIterator<Item = ExchangeRate>>(iter: I) -> Self
fn from_iter<I: IntoIterator<Item = ExchangeRate>>(iter: I) -> Self
Source§impl Hash for ExchangeRate
impl Hash for ExchangeRate
Source§impl PartialEq for ExchangeRate
impl PartialEq for ExchangeRate
Source§impl Serialize for ExchangeRate
Available on crate feature serde only.Writes three named fields: the two currencies of the pair and the rate
between them.
impl Serialize for ExchangeRate
serde only.Writes three named fields: the two currencies of the pair and the rate between them.
§Example
use lucre::{Currency, ExchangeRate};
use rust_decimal::dec;
let rate = ExchangeRate::new((Currency::USD, Currency::EUR), dec!(0.9))?;
assert_eq!(
serde_json::to_string(&rate)?,
r#"{"base":"USD","quote":"EUR","rate":"0.9"}"#
);