• Labs icon Lab
  • Core Tech
Labs

Guided: Building a Command Line Calculator in Rust

This lab is designed to walk you through the implementation for a command-line calculator in Rust. Throughout this lab, you will demonstrate your grasp of fundamental Rust concepts and systems such as Cargo, Ownership and Borrowing with References, Error Handling with Results, and Structs. By the end of this lab, you will have leveraged these fundamentals to build a working calculator capable of performing arithmetic calculations.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 45m
Published
Clock icon Sep 06, 2023

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    In this lab, you will be implementing a command-line calculator using Rust. This calculator will cover arithmetic operations such as addition, subtraction, multiplication, division, and exponentiation. You will also be implementing a history for storing up to 10 calculations which will display after each calculation.

    Key concepts that you will be familiarizing yourself here will be the following:

    • Ownership and Borrowing, which is integral to any Rust application
    • Cargo, Rust's package manager similar to npm
    • Error Handling with the Result enum
    • Structs

    At the end of the lab, your application will be runnable by clicking the Run button or by entering cargo run in the Terminal. If you are stuck, check out the solution directory.

  2. Challenge

    Step 1: Arithmetic Functions

    Take a look at the calculations.rs file. There are multiple ostensibly named arithmetic functions defined for you, although they only return dummy values for compilation purposes. You will need to correctly implement every function in this file.

    Note In the method signatures, every function `fn` is prefixed with the keyword `pub`, which makes the method public and therefore visible to other files and modules. The parameters for each method are typed, in this case 64-bit floats, much like you would see in other typed languages. The segment `-> f64` means that the function is expected to return a `f64` value. In Rust, the typical way to `return` a value is to simply make the statement without ending it in a `;`. For instance, `return "this is a string";` would just be `"this is a string"` or `return 2+2;` would just be `2+2`.

    While the previous methods were fairly straightforward, you will notice that divide() is slightly different. As you can see in the signature for divide(), it returns a Result<f64, String> instead of just an f64.

    This is an introduction to Results is Rust, which are enums that can be either Ok() or Err(). When performing operations that could potentially have errors, such as dividing by zero or retrieving a file from a path that does not exist, you need to be able to handle those potential errors. If the operation is successful, you return an Ok(value) with the value you wish to return enclosed in the Ok(). Otherwise, you return an Err(err) with a specified type enclosed in Err.

    In the case of divide(), which specifies Result<f64, String>, you would be returning an f64 as value inside Ok(value) and a String message as err within Err(err).

  3. Challenge

    Step 2: Utility Functions

    Take a look at util.rs. Don't worry about the use and #![allow(unused)] statements, as those are just for imports and to suppress warnings from the compiler. Aside from those, there isn't much else new here.

    The two validate functions are ostensibly to ensure that whatever input they are given can either be parsed into a number or is an arithmetic operator for calculations instead of invalid input such as words and phrases. The check_quit() function is mainly to check whether the user inputs the word "quit", which will proceed to exit the program early.

    The third function check_quit does have something a little different. The parameter input is marked as &String instead of String as seen previously. This means that it should be a pointer which references a piece of data rather than directly owning the data. This is necessary for certain types such as String, Vectors, Arrays, etc. because if a function claims ownership, that piece of data will go out of scope after the function ends. This means that if you define a String variable in main and pass it into check_quit, that variable would no longer be valid in main after the function call. By prefixing the parameter with & to make it a pointer, the function simply "borrows" the variable so that it will not go out of scope after the function call.

    Note Passing in primitive values like booleans and numbers is fine because they are typically small and of predefined size, so Rust is able to make a copy of them without dropping them. For more variable sized types such as Structs, Strings, etc. then references are necessary.
  4. Challenge

    Step 3: Calculation History

    Finally, head to history.rs. You will notice the definition of CalcHistory as a struct. In Rust, object-oriented programming is typically achieved using structs and enums(such as Result) rather than the typical class-based approach of other languages.

    As you can see, CalcHistory contains a field or property, called entries, which should be a Vec<String>. To implement methods for CalcHistory, you must use impl and define all methods within it. The new() method for CalcHistory has already been defined for you, which functionally acts as a constructor for the struct. Your task will be to implement add_entry(), view_history(), and clean_history().

    Note The `add_entry()` and `clean_history()` methods have `&mut self` instead of `&self` for their parameters. This is because they will be modifying `self` by adding or removing data from `entries`. In `view_history()`, nothing is being modified so it doesn't need `mut`. The Rust compiler does a great job at letting you know when you need to add or remove the `mut` keyword among others. It's very handy!
    With these completed, your command-line calculator is finished! You can take a look at `main.rs` if you'd like to see how your functions are used there.

    Running the application will prompt you to input the first number, an operation, and a second number. Or input "quit" at any time to exit the application. The methods you implemented in calculation.rs are then called depending on the specified operation, while the methods you defined in util.rs will cause an error message to display when invalid input is given. Lastly, after each calculation you will see up to 10 of your most recent calculation entries thanks to the CalcHistory you finished implementing.

George is a Pluralsight Author working on content for Hands-On Experiences. He is experienced in the Python, JavaScript, Java, and most recently Rust domains.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.