1 unstable release
new 0.4.0 | Mar 14, 2025 |
---|---|
0.3.0 |
|
0.2.0 |
|
0.1.0 |
|
#74 in Finance
153 downloads per month
32KB
221 lines
Brief description
"Down-to-earth" (explained further below) money [^fowler] and currency implementation written in Rust.
See further below for more detailed design considerations. [^fowler]: Money consists of an amount and a Currency, as described by Martin Fowler in his Money EAA Pattern.
Usage
// You create a Currency either with a string code ...
let eur = Currency::new("EUR", 2)?;
// ... or a byte array ...
let kwt = Currency::new_using_bytes(b"KWD", 2)?;
// ... you need a byte array also to create a constant (compiler doesn't allow unwrap) ...
const EUR: Currency = Currency::new_using_bytes_const(b"EUR", 2);
// ... and use either to instantiate Money instances:
let money1 = Money { full_amount_as_minor: 12_95, currency: eur };
let money2 = Money { full_amount_as_minor: 15_99, currency: EUR };
let result = money1 + money2;
Other implementations
Simple Money has similar implementations in other programming languages. There's a Simple Money project page on BitBucket, but I haven't published all of them.
Design considerations
Unless otherwise noted, the design considerations apply to all implementations, regardless of programming language.
Overview
The "down-to-earth" in the brief description above boils down to:
- no fractional minor units (e.g. one dollar has 100 cents, but there's no half cent)[^fracminor]
- Monetary amount and currency always go together, just as suggested in Martin Fowler (2002) Patterns of Enterprise Application Architecture, p. 488
- Intentionally no currency conversion (conversion rates are constantly changing and depend on external sources such as rate curves on the stock exchange)
Safe to use API:
- value object (concrete class), see further below
- no floating-point types in the API (
float
/f32
ordouble
/f64
), just stores the whole amount converted to the minor unit (cents, pence, …) - no accidental rounding (API forces you to either deal with division reminders or specify a rounding mode beforehand)
Compact size (not that relevant for all programming languages):
- small[^small] and copyable (if the programming language supports this) structs…
- the whole amount converted to the minor unit (cents, pence, …) is stored in a single
long
/i64
- In programming languages without shared immutable Strings, the
Currency
fits into four bytes
- the whole amount converted to the minor unit (cents, pence, …) is stored in a single
- …but still reasonably large enough minimum/maximum amounts (the whole US national dept in cents fits in hundreds of times in an
long
/i64
)
Other design decisions:
- no dependencies (either none at all or none when using it, depending on implementation variant, e.g. the Java variant has no transitive dependencies)
- no silent overflow --- although "reasonably sized" (as stated above), the functions will throw an exception (Rust: panic) in the unlikely event of an overflow (even in release builds)
Why runtime checks?
The implementations in statically typed programming languages might, just might, have leveraged the type system for the currencies, but I decided against it:
- simpler API: arithmetic operations intentionally work on and between
Money
instances for simplicity (returningOptional
s for all operations is too cumbersome; I only have the division return aDivisionResult
or similar type) - locking down via the type system is too problematic once you go down the rabbit hole (see also why not having currencies as type parameters?)
- drawback of the runtime checks: runtime exceptions can easily be ignored by a developer – do consider implementing Ward Cunningham's money bag if you have the need to mix currencies frequently
Value class
This class is a value object/concrete class:
- no identity-based equality, see e.g. Eric Evans (2003) Domain Driven Design, page 97 and Martin Fowler (2002) Patterns of Enterprise Application Architecture, page 486
- immutable, except for Rust and C++, where the bindings (“variables”) have mutability or immutability (C++: const correctness)
- no inheritance/polymorphism, see Bjarne Stroustrup, The C++ Programming Language, 4th edition, page 478[^cpp4] (16.3.4 The Significance of Concrete Classes)
Quote from "The C++ Programming Language, 4th edition" (Bjarne Stroustrup):
Concrete types have also been called value types and their use value-oriented programming. Their model of use and the "philosophy" behind their design are quite different from what is often called object-oriented programming (§3.2.4, Chapter 21).
The intent of a concrete type is to do a single, relatively simple thing well and efficiently. It is not usually the aim to provide the user with facilities to modify the behavior of a concrete type. In particular, concrete types are not intended to display run-time polymorphic behavior (see §3.2.3, §20.3.2).
(16.3.4 The Significance of Concrete Classes, page 478)
Note that Stroustrup's concrete types / value types are not necessarily immutable, contrary to the value types in Java or other languages which share objects across multiple places. In C++ (and Rust), the bindings (“variables”) have mutability or immutability (const correctness).
[^fracminor]: Of course, there are use cases where you want exactly that, use something like rusty-money for that.
[^small]: I'll refer to the C++ Core Guidelines
here, which deems 2 * sizeof(void*)
as "cheaply copyable" --- this should be equivalent to
mem::size_of::<usize>
in Rust (2 * 64bits on an 64bit architecture).
[^cpp4]: Quote from "The C++ Programming Language, 4th edition" (Bjarne Stroustrup):
Concrete types have also been called value types and their use value-oriented programming. Their model of use and the "philosophy" behind their design are quite different from what is often called object-oriented programming (§3.2.4, Chapter 21).
The intent of a concrete type is to do a single, relatively simple thing well and efficiently. It is not usually the aim to provide the user with facilities to modify the behavior of a concrete type. In particular, concrete types are not intended to display run-time polymorphic behavior (see §3.2.3, §20.3.2).
(16.3.4 The Significance of Concrete Classes, page 478)
Comparison to similar libraries
Here are two alternatives I've found:
- steel-cent
- uses an i64 for the amount, just like this implementation
- has the same precision as the minor amount allows (no "half Cents")
- its currency has the
Copy
trait and is as small as mine - the ISO currencies are provided, but IntelliJ's Rust plugin fails to see them (works in the terminal though)
- rusty-money:
- uses its own 128 representation for the amount
- has more precision than available by the minor amount
- its currency...
- has lots of fields, ...
- ... but the ISO currencies are provided ready to use
- has the
Copy
trait, but is eighty bytes in size
- is bristling with features (e.g. exchange rates?)