- Lab
- Core Tech

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.

Path Info
Table of Contents
-
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. -
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 fordivide()
, it returns aResult<f64, String>
instead of just anf64
.This is an introduction to
Results
is Rust, which are enums that can be eitherOk()
orErr()
. 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 anOk(value)
with the value you wish to return enclosed in theOk()
. Otherwise, you return anErr(err)
with a specified type enclosed inErr
.In the case of
divide()
, which specifiesResult<f64, String>
, you would be returning anf64
asvalue
insideOk(value)
and aString
message aserr
withinErr(err)
. -
Challenge
Step 2: Utility Functions
Take a look at
util.rs
. Don't worry about theuse
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. Thecheck_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 parameterinput
is marked as&String
instead ofString
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 aString
variable inmain
and pass it intocheck_quit
, that variable would no longer be valid inmain
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. -
Challenge
Step 3: Calculation History
Finally, head to
history.rs
. You will notice the definition ofCalcHistory
as astruct
. In Rust, object-oriented programming is typically achieved using structs and enums(such asResult
) rather than the typical class-based approach of other languages.As you can see,
CalcHistory
contains a field or property, calledentries
, which should be aVec<String>
. To implement methods forCalcHistory
, you must useimpl
and define all methods within it. Thenew()
method forCalcHistory
has already been defined for you, which functionally acts as a constructor for the struct. Your task will be to implementadd_entry()
,view_history()
, andclean_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!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 incalculation.rs
are then called depending on the specified operation, while the methods you defined inutil.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 theCalcHistory
you finished implementing.
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.