# lucre
[](https://ci.cosmicrose.dev/repos/43)
[](https://crates.io/crates/lucre)
[](https://docs.rs/lucre)
An ergonomic Rust library for handling money.
Represent money in your applications without fussing over generics or
lifetimes, while still being safe and fast. [ISO 4217] currency definitions are
built in.
[ISO 4217]: https://en.wikipedia.org/wiki/ISO_4217
## Install
Add `lucre` to your `Cargo.toml`.
```toml
[dependencies]
lucre = "0.9.0"
```
## Usage
The `Money` struct is the primary interface, with the `Currency` struct supporting it.
`Currency` contains constants for all current ISO 4217 currencies.
```rust
use lucre::{Money, Currency, Format, MoneyError};
fn main() -> Result<(), MoneyError> {
// Create money from major or minor units
let subtotal = Money::from_major(100, &Currency::USD);
let tax = Money::from_minor(475, &Currency::USD);
// Arithmetic is available as either checked or panicking operations
let _unchecked = subtotal + tax;
let total = subtotal.checked_add(&tax)?;
// The display format has sensible defaults...
assert_eq!(total.to_string(), "104.75 USD");
// ...which can be overridden
let format = Format::default().symbol();
assert_eq!(total.format_with(&format).to_string(), "$104.75");
Ok(())
}
```
### Several currencies at once
`Money` refuses to mix currencies — `+` panics and comparisons answer `None`.
When amounts in different currencies have to travel together, put them in a
`MoneyBag`, which keeps each currency on its own books.
```rust
use lucre::{Currency, Money, MoneyBag};
let mut wallet = MoneyBag::new();
wallet += Money::from_major(25, &Currency::USD);
wallet += Money::from_major(10, &Currency::EUR);
assert_eq!(
wallet.balance(&Currency::USD),
Money::from_major(25, &Currency::USD)
);
// A currency the wallet has never held answers zero, not nothing
assert_eq!(
wallet.balance(&Currency::JPY),
Money::from_major(0, &Currency::JPY)
);
// Both sums are available over the same iterator, so the type asked for is
// the decision: `Option<Money>` insists the currencies match, a bag keeps
// them apart
let refunds = [
Money::from_minor(1999, &Currency::USD),
Money::from_minor(1250, &Currency::USD),
];
assert_eq!(
refunds.iter().sum::<Option<Money>>(),
Some(Money::from_minor(3249, &Currency::USD))
);
assert_eq!(refunds.iter().sum::<MoneyBag>().to_string(), "32.49 USD");
// Once the currencies disagree, only the bag still answers
let mixed = [
Money::from_minor(1999, &Currency::USD),
Money::from_minor(1250, &Currency::EUR),
];
assert_eq!(
mixed.iter().sum::<MoneyBag>().to_string(),
"12.50 EUR, 19.99 USD"
);
```
Converting between currencies needs exchange rates, which are live data rather
than a standard, so they are out of scope here. Iterating a bag yields each
currency's balance in ISO alphabetic order, which is enough to apply rates of
your own.
## Features
### `serde`
Off by default. Turning it on gives `Money`, `MoneyBag`, `Currency`,
`IsoAlphabeticCode`, `IsoNumericCode`, and `RoundingMode` a `Serialize` and a
`Deserialize` impl.
```toml
[dependencies]
lucre = { version = "0.9.0", features = ["serde"] }
```
Amounts travel as text, which carries a fraction exactly and keeps the scale
the amount was built with. Numbers are read as well, floats included, but only
text makes the round trip untouched.
```json
{ "amount": "104.75", "currency": "USD" }
```
A bag is a balance per currency, keyed by ISO alphabetic code. Reading one
totals whatever the document says, rather than insisting it already be in the
shape a bag would have written: a zero balance leaves no currency behind, and
a currency named twice is summed.
```json
{ "EUR": "10.00", "USD": "30.00" }
```
The smaller types are single values rather than objects:
| `Currency` | `"USD"` | the three-letter code, unassigned codes refused |
| `IsoAlphabeticCode` | `"ZZZ"` | three capitals, assigned or not |
| `IsoNumericCode` | `840` | an integer of at most three digits |
| `RoundingMode` | `"half-up"` | or `"half-down"`, or `"half-even"` |
JSON, TOML, YAML and their kin work. Formats that carry no type tags, such as
bincode and postcard, do not.
## Maintainer
This project is maintained by [Rosa Richter](https://cosmicrose.dev).
For ways to contact her, see her [contact page](https://cosmicrose.dev/contact/).
## Contributing
Questions and contributions are absolutely welcome!
Please [create an issue](https://code.cosmicrose.dev/rosa/lucre/issues/new)
for bugs, feature requests, or questions.
## License
[BSD-2-Clause-Patent](https://spdx.org/licenses/BSD-2-Clause-Patent.html) © Rosa Richter