Building a Simple Bookkeeping App with Rust
Published: 2023-06-14 14:33:56

Rust, renowned for its emphasis on performance, safety, and ease of use, is an excellent choice for developing CLI applications. This project serves the dual purpose of gaining hands-on experience with Rust and providing a comprehensive guide for developers interested in leveraging its capabilities for building bookkeeping apps.

In the upcoming sections, we will embark on a step-by-step journey to construct a bookkeeping application from scratch. We will cover everything from setting up the development environment to packaging and distributing the final application. Whether you are new to Rust or have prior experience, this article aims to equip you with practical knowledge and insights specific to building CLI apps with Rust, with a focus on bookkeeping functionality.

So, without further ado, let's roll up our sleeves and embark on this Rust-filled journey of building a bookkeeping app.


![via Giphy](https://media.giphy.com/media/3oFzm0Ocr6CC0URLsA/giphy.gif)

## Setting Up the Development Environment

Before we delve into building our bookkeeping app using Rust, let's ensure our development environment is properly set up. Follow these steps to ensure a smooth and efficient workflow:

1. **Install Rust**: Begin by installing Rust on your machine. Visit the official [Rust website](https://www.rust-lang.org/tools/install) and follow the instructions specific to your operating system. Rust provides an easy-to-use installer that handles the entire installation process for you.

2. **Verify the Installation**: Once Rust is installed, open your terminal or command prompt and enter the following command:

   ```bash
   rustc --version
   ```

   This command will display the version of Rust installed on your system. If you see the version number, it means Rust has been successfully installed.

3. **Editor Setup**: Choose your preferred code editor or integrated development environment (IDE) for Rust development. Popular options include Visual Studio Code with the Rust extension, IntelliJ IDEA with the Rust plugin, or Atom with the Rust package. Install the necessary plugins or extensions to enable Rust development in your chosen editor.

4. **Create and Initialize a New Project**: To begin building our bookkeeping app, we need to create and initialize a new Rust project. Open your terminal or command prompt and navigate to your desired project directory. Then, run the following command:

   ```bash
   cargo new bookkeeping-app
   ```

   This command will create a new directory named "bookkeeping-app" with the necessary files and folder structure for a Rust project. It will also generate the `Cargo.toml` file, which manages dependencies and build configurations.

Congratulations! You have successfully set up the project directory and initialized a new Rust project for building the bookkeeping app. In the next section, we will discuss the application's design and outline its key features and components.

## Designing the Bookkeeping App

Now that we have our development environment set up, let's focus on the design of our bookkeeping app. The application will be simple, with the following features:

- Create a debit record
- Create a credit record
- Check the balance (where a negative balance indicates debt)
- List all records in the order they were added

Additionally, we can consider the following bonus features:

- Generate a summary highlighting the total debit, total credit, and current balance
- Generate a report that separates debit records from credit records
- Include an option to start with a non-zero balance
- Add persistence to the records, allowing them to survive between app runs

To represent a single record, we will use a `f32`, and the ledger will be a vector of `f32` to accommodate dynamic sizing. Since we are not implementing data persistence, a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) interface will be the most suitable for handling commands. In the following section, we will

 discuss the expected input patterns for the program.

### Input Patterns

The expected input patterns for the bookkeeping app are as follows:

1. Create a debit record: `DEBIT <amount>`
2. Create a credit record: `CREDIT <amount>`
3. Check the balance: `BALANCE`
4. List records: `LIST`
5. End the program: `QUIT`
6. Display program help: `HELP`

Any input that does not match these specified patterns will be considered invalid and flagged by the program.

With this information, we have everything we need to start building our bookkeeping app. Let's dive into the implementation details in the upcoming sections.

<!-- TODO: Insert MEME of a person rolling up their sleeves -->

## Implementation Details

We'll start by defining some helper functions that will be required for the app's functionality. Update the code in the `main.rs` file with the following implementation details:

```rust
use std::{io::Write};

fn flag_invalid_input(input: &str) {
    println!("Invalid input: {}\n", input);
    print_help();
}

fn print_help() {
    println!("----- HELP -----");
    println!(
        r"Commands are not case sensitive

DEBIT <amount>: Create a debit record
CREDIT <amount>: Create a credit record
BALANCE: Check the balance
LIST: List records
QUIT: End the program
HELP: Display this help message"
    );
}

/// Helper function to add a record to the list
fn add_record(records: &mut Vec<f32>, amount: f32) {
    println!("Adding entry: {}", amount);
    records.push(amount)
}

fn create_debit_record(records: &mut Vec<f32>, amount: f32) {
    add_record(records, -amount);
}

fn create_credit_record(records: &mut Vec<f32>, amount: f32) {
    add_record(records, amount);
}

fn get_balance(records: &[f32]) -> f32 {
    records.iter().sum()
}

/// Helper function to format records in a user-readable format
fn format_records_to_string(records: &[f32]) -> String {
    let mut output = String::new();
    for entry in records {
        let t = if entry < &0.0 { "Debit\t" } else {"Credit\t"};
        let amount = entry.abs().to_string();
        output.push_str(&(t.to_owned() + &amount));
        output.push('\n');
    };
    output
}

fn main() {
    println!("Welcome to the Bookkeeping App!\n");
    let mut records: Vec<f32> = vec![0.0; 0];
    print_help();
    println!("\nHappy bookkeeping!\n");

    let mut input = String::new();
    loop {
        // Collect user input
        print!(">>> ");
        std::io::stdout().flush().expect("Failed to flush stdout");
        std::io::stdin()
            .read_line(&mut input)
            .expect("Failed to read line from stdin");
        input = input.trim().to_ascii_lowercase();

        let tokens: Vec<&str> = input.split_whitespace().collect();
        let command = tokens.get(0).copied();
        let amount = tokens.get(1).and_then(|s| s.parse::<f32>().ok());

        match command {
            None => {}
            Some("quit") => break,
            Some("help") => print_help(),
            Some("list") => println!("{}", format_records_to_string(&records)),
            Some("balance") => println!("{}", get_balance(&records)),
            Some("credit") => match amount {
                Some(amount) => create_credit_record(&mut records, amount),
                None => flag_invalid_input

(&input),
            },
            Some("debit") => match amount {
                Some(amount) => create_debit_record(&mut records, amount),
                None => flag_invalid_input(&input),
            },
            _ => flag_invalid_input(&input),
        };

        // Clear the input buffer for the next input
        input.clear();
    }

    println!("\nGoodbye! See you next time.")
}

```

The code now includes the helper functions for handling invalid input, printing help, getting ordered records, adding records, creating debit and credit records, getting the balance, and formatting records to a user-readable string.

The `main` function has been updated to handle user input, parse commands and amounts, and execute the corresponding actions. The input is trimmed, converted to lowercase, and split into tokens using whitespace. The first token represents the command, and the second token (if present) represents the amount. The program then matches the command and amount (if applicable) to the corresponding actions or displays an invalid input message.

With these implementation details in place, we have laid the foundation for our bookkeeping app.

![Application interaction session](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/deaa8hl85ec8kr16va5u.png)

## Conclusion 

In this article, we have explored the process of building a bookkeeping app using Rust. We started by setting up the development environment and initializing a new Rust project. Then, we discussed the design of the app, including its features and input patterns.

Next, we implemented the core functionality of the app, including helper functions for handling invalid input, printing help, adding records, creating debit and credit records, getting the balance, and formatting records for display. We also updated the `main` function to handle user input, parse commands and amounts, and execute the corresponding actions.

Throughout the process, we leveraged Rust's powerful features, such as pattern matching, error handling, and vector manipulation. By following the step-by-step implementation and understanding the code snippets, you gained practical knowledge of Rust's core concepts and their application in building CLI apps.

While this guide focused on the fundamentals of building a bookkeeping app, there are still many opportunities for improvement and expansion. Some potential enhancements include data modeling, command-line argument parsing, data persistence, generating summaries and reports, and packaging and distributing the final application. These aspects can be explored and implemented based on your specific needs and requirements.

Rust's performance, safety, and ease of use make it an excellent choice for developing CLI applications. By applying the knowledge and skills gained from this guide, you are well-equipped to tackle a wide range of CLI projects with Rust.

Now it's time to apply what you've learned and start building your own Rust-powered CLI apps. Happy coding!