| Crates.io | typed-money |
| lib.rs | typed-money |
| version | 0.2.0 |
| created_at | 2025-10-19 23:14:09.669895+00 |
| updated_at | 2025-10-20 18:06:01.54477+00 |
| description | A type-safe money library for Rust that prevents currency mixing bugs at compile time |
| homepage | https://github.com/ricardoferreirades/typed-money |
| repository | https://github.com/ricardoferreirades/typed-money |
| max_upload_size | |
| id | 1891178 |
| size | 593,844 |
A type-safe money library for Rust that prevents currency mixing bugs at compile time. Zero runtime overhead, maximum safety.
Typed Money solves a fundamental problem in financial software: preventing currency mixing errors that can lead to costly bugs, incorrect calculations, and financial losses. Traditional approaches rely on runtime checks or primitive types that offer no protection against accidentally adding dollars to euros or mixing different currency units.
This library provides compile-time type safety for monetary values, ensuring that currency operations are explicit and safe. Every currency is a distinct type, making it impossible to accidentally mix currencies in arithmetic operations. The compiler catches these errors before your code ever runs, eliminating an entire class of financial bugs.
// Traditional approach - runtime errors possible
let usd_balance = 1000.0;
let eur_balance = 850.0;
let total = usd_balance + eur_balance; // Bug! Mixing currencies
// Type-safe approach - compile-time safety
let usd_balance = Amount::<USD>::from_major(1000);
let eur_balance = Amount::<EUR>::from_major(850);
// let total = usd_balance + eur_balance; // Compile error - caught at build time!
Add to your Cargo.toml:
[dependencies]
typed-money = "*"
use typed_money::{Amount, USD, EUR};
// Create amounts from major units (dollars, euros)
let usd_amount = Amount::<USD>::from_major(100); // $100.00
let eur_amount = Amount::<EUR>::from_major(85); // €85.00
// Create amounts from minor units (cents, euro cents)
let usd_cents = Amount::<USD>::from_minor(10000); // $100.00
let eur_cents = Amount::<EUR>::from_minor(8500); // €85.00
use typed_money::{Amount, USD};
let price = Amount::<USD>::from_major(99); // $99.00
let tax = Amount::<USD>::from_minor(990); // $9.90
let total = price + tax; // $108.90
// Scalar operations
let discounted = total * 2; // $217.80
let half_price = total / 2; // $54.45
// This won't compile - different currencies
// let mixed = price + Amount::<EUR>::from_major(10); // Error!
use typed_money::{Amount, Rate, USD, EUR};
let usd_amount = Amount::<USD>::from_major(100);
let rate = Rate::<USD, EUR>::new(0.85);
let eur_amount = usd_amount.convert(&rate); // €85.00
// Rate with metadata for auditability
let rate_with_metadata = Rate::<USD, EUR>::new(0.85)
.with_timestamp_unix_secs(1_700_000_000)
.with_source("ECB");
use typed_money::{Amount, RoundingMode, USD};
let amount = Amount::<USD>::from_major(100) + Amount::<USD>::from_minor(5); // $100.05
let rounded_up = amount.round(RoundingMode::HalfUp); // $100.05 → $100.05
let rounded_down = amount.round(RoundingMode::HalfDown); // $100.05 → $100.05
let bankers = amount.round(RoundingMode::HalfEven); // $100.05 → $100.05
use typed_money::{Amount, MoneyError, USD};
match Amount::<USD>::parse("$100.50") {
Ok(amount) => println!("Parsed: {}", amount),
Err(MoneyError::ParseError { input, reason }) => {
println!("Failed to parse '{}': {}", input, reason);
}
Err(e) => println!("Error: {}", e),
}
use typed_money::{Amount, USD};
use serde_json;
let amount = Amount::<USD>::from_major(100);
let json = serde_json::to_string(&amount)?; // "{\"value\":\"100.00\",\"currency\":\"USD\"}"
let deserialized: Amount<USD> = serde_json::from_str(&json)?;
use typed_money::{Amount, USD, EUR, BRL, CurrencyMetadata};
// Access rich currency metadata
let usd_amount = Amount::<USD>::from_major(1234);
println!("Currency: {}", usd_amount.currency_name()); // "US Dollar"
println!("Country: {}", usd_amount.currency_country()); // "United States"
println!("Region: {}", usd_amount.currency_region()); // "North America"
println!("Type: {}", usd_amount.currency_type()); // "Fiat"
println!("Volatility: {}", usd_amount.volatility_rating()); // "Low"
println!("Liquidity: {}", usd_amount.liquidity_rating()); // "High"
// Locale-specific formatting
let eur_amount = Amount::<EUR>::from_major(1234);
println!("European format: {}", eur_amount); // "€1.234,00 EUR"
let brl_amount = Amount::<BRL>::from_major(1234);
println!("Brazilian format: {}", brl_amount); // "R$1.234,00 BRL"
use typed_money::{Amount, XAU, XAG, XPT, XPD, XDI, XCU, XAL, CurrencyMetadata};
// Precious metals with 4 decimal precision
let gold = Amount::<XAU>::from_major(1); // Au1.0000 XAU
let silver = Amount::<XAG>::from_major(100); // Ag100.0000 XAG
let platinum = Amount::<XPT>::from_major(1); // Pt1.0000 XPT
let palladium = Amount::<XPD>::from_major(1); // Pd1.0000 XPD
let diamond = Amount::<XDI>::from_major(1); // Di1.0000 XDI
// Base metals
let copper = Amount::<XCU>::from_major(1000); // Cu1000.0000 XCU
let aluminum = Amount::<XAL>::from_major(1000); // Al1000.0000 XAL
// Access commodity metadata
println!("Gold region: {}", gold.currency_region()); // "Worldwide"
println!("Gold type: {}", gold.currency_type()); // "Commodity"
println!("Gold volatility: {}", gold.volatility_rating()); // "Medium"
use typed_money::{Currency, Amount};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct CAD; // Canadian Dollar
impl Currency for CAD {
const DECIMALS: u8 = 2;
const CODE: &'static str = "CAD";
const SYMBOL: &'static str = "C$";
}
let cad_amount = Amount::<CAD>::from_major(100); // C$100.00
The library supports 69 currencies across multiple categories:
See CURRENCIES.md for a complete reference.
Benchmarked on Apple M1 Pro:
| Operation | Time | Throughput |
|---|---|---|
| Addition | ~4.9ns | 204M ops/sec |
| Subtraction | ~5.0ns | 200M ops/sec |
| Currency Conversion | ~5.0ns | 200M ops/sec |
| Rounding | ~5.0ns | 200M ops/sec |
| Equality Check | ~3.2ns | 312M ops/sec |
See PERFORMANCE.md for detailed benchmarks.
[dependencies]
typed-money = { features = ["serde_support", "conversion_tracking"] }
serde_support - Enable JSON serializationconversion_tracking - Track currency conversions for auditinguse_bigdecimal - Use bigdecimal instead of rust_decimal[dependencies]
typed-money = { default-features = false }
# Basic usage
cargo run --example basic_usage
# Currency conversions
cargo run --example conversions
# Rounding modes
cargo run --example rounding
# Error handling
cargo run --example error_handling
# Serialization
cargo run --example serialization
# Custom currencies
cargo run --example custom_currency
# Internationalization features
cargo run --example internationalization
# Currency metadata
cargo run --example currency_metadata
# Precious metals
cargo run --example precious_metals
| Feature | Typed Money | Traditional Libraries |
|---|---|---|
| Type Safety | Compile-time | Runtime checks |
| Performance | ~5ns operations | ~50-100ns |
| Currency Mixing | Impossible | Runtime errors |
| Precision | Deterministic | Floating-point errors |
| Documentation | Comprehensive | Often minimal |
| Testing | 437 tests | Variable coverage |
We welcome contributions! Please see our Contributing Guide for details.
Dual-licensed under MIT OR Apache-2.0. See LICENSE-MIT for details.
Made with Rust | Report Bug | Request Feature