Open-source accounting for personal and professional finances

rledger is a lightweight double-entry accounting tool. It's based on the principles of plain text accounting and written in Rust, making it robust and extremely fast.

Brought to you by Alexander Keliris

Add the print command

It's often useful to simply print the journal, particularly to see inferred amounts or to find a specific transaction.

rledger now supports the print command with the optional explicit toggle for printing inferred amounts: print -x.

Performance boost 🚀

Performance took a hit after implementing the balance assertions feature. After parsing the .journal, we then need to sort transactions/postings by date and sum each account/commodity pair to check the balance assertions.

Looking at the code, there were some simple optimisations we could make. Namely, reducing allocations.

By changing .into_iter() to .iter() and borrowing values instead of .clone(), we clawed back some of that lost performance.

There are plenty of other opportunities to improve performance further in the future: experiment with parallel processing (probably using rayon), using more optimised data structures, and reducing yet more allocations.


  • 65% speed up!

Date Range Filters

You can now add date range filters before the report commands.

-b, --begin <DATE>  include postings/txns on or after this date
-e, --end <DATE>    include postings/txns before this date

For example, to see your income statement for the UK 22/23 tax year:

rledger -f 2023.journal -b 2022-04-06 -e 2023-04-06 incomestatement

Balance assertions and assignments

Postings can contain "balance assertions" and "balance assignments".

2023-05-04 opening balances
    assets:bank  = $500 ; <- this is an "assignment"
    equity:opening/closing balances

    assets:bank  $1000 = $1500 ; <- this is an "assertion"
    income:work  $-1000


After reading a journal file, rledger will check all balance assertions and report an error if any of them fail. Balance assertions can protect you from e.g. inadvertently disrupting reconciled balances while cleaning up old entries.


These are like balance assertions, but with no posting amount on the left side of the equals sign; instead it is calculated automatically to satisfy the assertion. This can be a convenience during data entry e.g. when setting opening balances.


  • Assertions provide powerful error checking when adding new transactions.
  • Assignments are a convenient way to declare opening balances or asset valuations for things like stock or mutual fund investments.

Example assertion error:

More directive support

In the .journal file, you can declare "directives" that influence how the journal is processed. We have already implemented the include directive (for including other .journal files in the main journal).

This update adds the account and commodity directives, which are useful for error checking. For example, checking that the accounts and commodities in the journal are expected (protect against typos).


  • Add the commodity directive
  • Add the account directive

Now, they have no impact on journal parsing. But we will use them when we implement strict checking later on.

Currency support

The commodity for a given amount controls how the amount will be displayed. I introduced the rusty_money crate to handle the formatting.


  • Amounts with a currency iso code will be displayed in the expected format for that currency.
  • For convenience, the following common currency symbols will be interpreted without the need for the iso code:
"$" => USD
"£" => GBP
"€" => EUR
"¥" => JPY

include support

Within a journal, you can now include other journal files:

include 2022.journal

2023-01-20 Client A | Invoice #1
    assets:receivables      $10,000.00
    revenue:clients:A      -$10,000.00


  • This feature allows you to break up large journal files and organise them how you like. A typical approach is to create yearly journal files e.g. 2022.journal, and then include all the years in a all-years.journal.
  • File paths are relative to the currently opened journal

Balance Report

The balance command produces a foundational report for listing account balances, balance changes, values, value changes and more, during one time period or many. Generally it shows a table, with rows representing accounts, and columns representing periods.

The higher level reports such as the balance sheet and income statement are special cases of the balance report.


  • This change also brought a small performance boost to the parser.


On an AMD Ryzen 9 5950X 16-Core Processor, I am getting 275,549 transactions per second 🚀.

By comparison, hledger is running at 13,982 per second.

rledger is now ~19x faster than hledger 🤯⚡.

Run benchmarks using criterion with cargo bench. This will show how performance has changed between runs.

Ad hoc tests can be taken with hyperfine:

cargo build --release
hyperfine './target/release/rledger -f benches/10000x1000x10.journal bs'

This approach is good for comparing performance with hledger (how the results above were found):

hyperfine -w 5 './target/release/rledger -f benches/10000x1000x10.journal bs' 'hledger -f benches/10000x1000x10.journal bs'

Integration testing

Since this is a plain text accounting CLI, we can make use of snapshot testing. The idea is that we run a CLI command given some input and compare the output with our expected result.

I had rolled my own testing framework using nom to parse the test files. However, a colleague of mine, Austin McBee, tipped me on to trycmd.

This crate provides a harness for test case files and asserting stdout/stderr are expected.


  • Unit tests can get verbose and snapshot testing concisely covers testing the CLI -> parsing journal -> producing a report or error.
  • trycmd allows for tests to be written in markdown files. This makes tests also serve as documentation and reduces the number of test files.
  • Works with pretty errors from miette

Reading from stdin

So far, rledger has only been able to read the journal from a file. But it can be convenient to read from stdin, allowing clean integration with other unix tools like cat, grep etc.

Use the argument -f - to read from stdin:

cat examples/example.journal | rledger -f - bs