Crates.io | simple_money |
lib.rs | simple_money |
version | 0.4.0 |
created_at | 2025-03-14 18:56:36.798746+00 |
updated_at | 2025-03-14 19:23:48.422398+00 |
description | Down-to-earth money and currency implementation written in Rust. |
homepage | |
repository | https://bitbucket.org/afischl/simplemoney-rust |
max_upload_size | |
id | 1592609 |
size | 43,551 |
"Down-to-earth" (explained further below) money 1 and currency implementation written in Rust.
See further below for more detailed design considerations.
// 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;
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.
Unless otherwise noted, the design considerations apply to all implementations, regardless of programming language.
The "down-to-earth" in the brief description above boils down to:
Safe to use API:
float
/f32
or double
/f64
), just stores the whole amount converted to the minor unit (cents, pence, …)Compact size (not that relevant for all programming languages):
long
/i64
Currency
fits into four byteslong
/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)
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 (returning Optional
s for all operations is too cumbersome; I only have the division return a DivisionResult
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
This class is a value object/concrete class:
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).
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)
Here are two alternatives I've found:
Copy
trait and is as small as mineCopy
trait, but is eighty bytes in sizeMoney consists of an amount and a Currency, as described by Martin Fowler in his Money EAA Pattern. ↩
Of course, there are use cases where you want exactly that, use something like rusty-money for that. ↩
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). ↩
Quote from "The C++ Programming Language, 4th edition" (Bjarne Stroustrup): ↩