Rust and Me
Some time ago I bought by son a book called “Infinity and Me”, about a girl who is trying to grasp the concept of infinity. Some weeks ago, I felt as disoriented and confused by Rust as that little girl with infinity.
Why I like Rust still eludes me. In recent years I have read books about Go, F# and Idris. I am on my fourth Rust book, and it has been the only language I have typed code outside of the examples in the books.1
It is definitely not something I will be using at work. We use Java and JavaScript, with traditional Spring Boot / React frameworks, up high in the cozy world of garbage-collected VMs. Go would be a more natural fit there.
In the past few weeks I have been writing a rust program to process financial statements. I am now quite happy with the result, but it is not yet finished. It is a rewrite of a node program I use about once a month.
My confusion with Rust stems from two main issues which could be synthesized into “I don’t usually write could in a style that fits well with Rust ownership model”.
1. The Sea of Objects
Java and frameworks like Spring Boot truly encourage “a sea of objects”, where you have many objects that know about each other and there is no clear “ownership”. You even have circular references sometimes. In the traditional MVC pattern, the view talks to the controller who talks to the view through callbacks, which are references after all.
Rust prefers a more tree-like ownership. Straying from that is a definite recipe for headaches.
The data model I am working with, a double-entry accounting ledger is not a neat tree-structure, but it is not a total “sea of objects”. The UI library I chose to use is cursive and it is callback-driven, so when I stared with my MVC mentality, it quickly back-fired. This problem is even described in the last part of Closures chapter in O’Reilly’s Programming Rust book. So at least I was warned.
The Ledger owns two lists, one of transactions, and one of accounts. A transaction owns a list of entries. Each entry references an account. As all objects are owned by the “root” ledger, there is no big issue here.
2. References and the borrow-checker
Though I do all of my work high in the software stack, I enjoy listening to people who work way below, in the hardware/software interface. Around the time that Oracle bought Solaris, I tried out OpenSolaris and somehow I came across some talk by Bryan Cantrill (probably about DTrace? not sure). I have been following him ever since, he has great talks. So when he co-founded Oxide Computer with Jessie Frazelle, whom I also followed on Twitter, I was intrigued. They had a podcast, On The Metal, and now weekly live shows Oxide and Friends. This is a very convoluted way of saying that I knew even experienced C programmers like Bryan had a hard time with Rust. I heard him and several others say it several times in podcasts and talks… laughing about being yelled, very politely, by the compiler and fighting the borrow-checker.
So when I sat down, started writing my small little program and run the tests, and everything seemed OK and just worked… I was very suspicious. I then said to myself, “Ok, I’ll write my iterator now” because I wanted the fancy for transaction in ledger.iter() syntax… and then, yes, the borrow-checker DID have something to say.
After several trial and errors, adding and removing lifetime parameters everywhere, finally re-reading about the ownership model and references, I decided to cut my losses and remove the object that seemed to be the source of all my sorrows. Namely the Money instances from the rutsy_money crate that I am using.
The problem with it is that it owns a decimal instance but only borrows a reference to a currency instance. I am well aware that currencies are basically value-objects, and that in a high-performance piece of software creating many instances of the same currency makes no sense. But storing them internally or returning them is quite annoying for a novice Rust programmer. I still use them for input/output, but for now, my Amount object just holds both a decimal and a currency instance, both owned, until I research a more idiomatic way of doing this in Rust.
What I like about Rust
It would be silly of me not to mention at least a few things which I do like about Rust, so here they go:
ADTs and Pattern Matching
I really like them. After reading more about functional programming, using algebraic data types is a breath of fresh air, and pattern matching with them is great. It seems so readable it looks like cheating, when compared to other type of code.
For example, one of the things I wanted to try out in my Rust experiment was modelling transaction balance errors like so:
pub enum BalanceError<'a, T: FormattableCurrency> {
Empty,
Single(Money<'a, T>),
Unbalanced(Money<'a, T>),
TooManyCurrencies,
TooManyImplicit,
}
impl<'a, T: FormattableCurrency> fmt::Display for BalanceError<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Empty | Single(_) => write!(f, "You must have at least two entry inputs"),
Unbalanced(money) => write!(f, "Entries are unbalanced by {}", money),
TooManyCurrencies => write!(f, "There are more than two currencies"),
TooManyImplicit => write!(f, "Only one entry can be implicit (have zero amount)"),
}
}
}
Forget about all the Rust specifics here. When doing the data-entry, I could call the get_balanced_input(&input) -> Result<BalancedInput, BalanceError> and have the UI either render a green “ok!”, or pattern-match against each option to help the user. For example, the data held in Unbalanced error can be used to auto-complete the missing entry amount.
Result<T, Error> and Option
I prefer Rust’s approach to error handling than Java’s exception model. I also find it more interesting than Go’s approach. Though I have not written much Go code outside programming books, I have read quite a bit of it in the real world. I do that Go picks a style and sticks with it, and you see it everywhere:
doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
}
But Rust’s approach seems to give the caller a better choice of what to actually DO with errors later.
Though Java has Option, it is not pervasive, and of course, it has nulls which gives rise to the infamous NullPointerException. Rust’s Option is very tightly integrated with the rest of the language, and I still have to learn the more idiomatic ways of using them. For example, at first I typed something like this (explicit types added for clarity)
// what I wrote first, knowing I had seen a better way
let old_opt: Option<LedgerTxn<T>> = self.txns.remove(&uuid);
if old_opt.is_none() {
return Err(NotFoundError { uuid });
}
let old: LedgerTxn<T> = old_opt.unwrap();
// when I finally sat down to refactor that
let old: LedgerTxn<T> = self.txns.remove(&uuid).ok_or(NotFoundError { uuid })?;
And other reasons
I like Rust’s approach of we can have nice things for systems programming. Rust’s iterators are very nice to use. The crates system works well. Their goal of being a memory-safe language without garbage collection, that can compete with C/C++ is an excellent one. It sounds almost criminal NOT to be using a memory-safe language nowadays. Go read New Report: Future of Memory Safety by Yael Grauer on Consumer Report or listen to her on Oxide and Friends for more context.
I also really enjoyed just installing rust, cargo and having a fully working environment integrated with package management, unit testing, documentation generation and more. Coming from the world of Java, Gradle, Maven, Junit, Junit5, AssertJ, … etc. it was… wow, just simple.
And lastly and probably least, I seem to like snake_case. I had used it a lifetime ago when writing C and in other PHP code bases. Maybe it is just nostalgia, or maybe now that I am older it seems more readable. Not sure.
The (not) final product
So this is what it looks like now:

Nothing fancy. This is showing the parsing of this fictional boring savings account CSV:
Status,Date,Description,Debit,Credit
Cleared,05-09-2023,"Interest Payment ",,0.76,
Cleared,05-09-2023,"Federal Withholding Tax ",0.22,,
Cleared,04-07-2023,"Interest Payment ",,0.42,
Cleared,04-07-2023,"Federal Withholding Tax ",0.24,,
Cleared,03-09-2023,"Interest Payment ",,0.65,
Cleared,03-09-2023,"Federal Withholding Tax ",0.20,,
Using this configuration file:
source_account = "Assets:Bank"
target_account = "Expenses:Unknown"
default_currency = "USD"
has_headers = true
date_format = "%m-%d-%Y"
date_index = 1
description_index = 2
credit_index = 4
debit_index = 3
[[replacements]]
re = "Interest Payment"
target = "Income:Interests"
This last bit means “if you see a description that matches the regular expression Interest Payment, make the target account Income:Interests”.
Then you press enter you get something like this:

Where I am showing the plain text accounting format, the raw CSV record, and the actions you can do with the record.
My current to-do list is:
- Externalizing CSV configuration ✅
- Saving the result to my customized JSON format ✅
- Adding new replacements and applying them ✅
- Saving working state to be resumed later ✅
- Saving new replacements ✅
- Deleting transactions ✅
- Modifying date, adding more entries, etc ❌
- Create credit card installments transactions ❌
- Undo actions ❌
My next big implementation will be the edit transaction feature, and once I tested it with my crappy Argentine bank data, I realized:
rusty_moneydoes not seem to parse decimals written with “decimal comma” (e.g. $ 10.000,12). So I just apply a simple transformation10.000,12 => 10000,12 => 10000.12when the config specifiesdecimal_comma = true- My bank generates multi-line CSV rows, which I had forgotten (I show the raw line in the details, but that feature is broken for multi-line CSV records).
By the way, no, I did not create a web application for this simple application, and I don’t think I will. I did try out slint which uses native GUI, and it seemed very easy to use. However, I preferred to start with something simpler.
I hope to write a followup entry about this project in a few weeks!
-
These are the books: The Rust Programming Language (I should re-read this one, as I read this several years ago while putting my son to sleep). Zero To Production In Rust (I really liked it. Regardless of Rust, it has some nice things in it). Programming Rust. Rust for Rustaceans (currently reading, liking it so far). Type-Driven Development with Idris (It is a great book, I would really like to write something else than the code examples in Idris). Domain Modeling Made Functional. The Go Programming Language (have to give Go another go. It seems so pragmatic!) ↩︎