We’ve covered quite a few basic topics about programming in Rust for a long time. some of these topics Variables, change, constantsAnd data typesAnd missionAnd if-else statements And episodes.
In the final chapter of the Rust Basics series, let’s now write a program in Rust that uses these topics so that their real-world use can be better understood. Let’s work on a file Relatively simple Program for ordering fruit from the fruit market.
The basic structure of our program
Let’s first start by greeting the user and telling them how to interact with the program.
fn main() {
println!("Welcome to the fruit mart!");
println!("Please select a fruit to buy.\n");
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
}
Get user input
The above code is very simple. Right now, you don’t know what to do next because you don’t know what the user wants to do next.
So let’s add code that accepts user input and stores it somewhere for later analysis, and takes appropriate action based on user input.
use std::io;
fn main() {
println!("Welcome to the fruit mart!");
println!("Plase select a fruit to buy.\n");
println!("Available fruits to buy: Apple, Banana, Orange, Mango, Grapes");
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
// get user input
let mut user_input = String::new();
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
}
There are three new items that I must tell you about. So let’s take a superficial look at each of these new items.
1. Understanding the βuseβ keyword
In the first line of this program, you may have noticed the use (haha!) of the new keyword called use
. the use
The Rust keyword is similar to #include
Orientation in C/C++ and import
keyword in Python. using use
keyword, we “import” a file io
(input output) from the Rust standard library std
.
You may be wondering why import a file io The module was necessary when you could use a println
macro production something to dodge. The Rust standard library contains a module called prelude
which are included automatically. The preamble module contains all commonly used functions that a Rust programmer might need to use, such as println
precise. (You can read more about std::prelude
lonliness here.)
the io
A module of the Rust standard library std
Necessary to accept user input. Hence, a use
The statement has been added to 1street line of this program.
2. Understanding the string type in Rust
On line 11, I created a new volatile variable called user_input
This, as its name suggests, will be used to store user inputs on the road. But by the same token, you might have noticed something new (haha, again!).
Instead of declaring an empty string using two quotes with nothing in between (""
), I used a file String::new()
Function to create a new empty string.
The difference between using ""
And String::new()
is something you will learn later in the Rust series. For now, find out with a file String::new()
function, you can create any string volatile live on pile.
If you have created a string with ""
, I’m going to get something called a “string slice”. The contents of the string segment are also in the heap, but the string itself is there Fixed. Therefore, even if the variable itself is mutable, the actual data stored as a string is immutable and it should be written instead of modification.
3. Accept user input
On line 12, call stdin()
which is part of std::io
. If I don’t include a std::io
module at the start of this program, it will be this line std::io::stdin()
instead of io::stdin()
.
the stdin()
The function returns the input handle of the station. the read_line()
The function grabs this input handle and, as its name suggests, reads a line of input. This function takes a reference to a mutable string. So, I passed in user_input
variable preceded by &mut
making it a variable reference.
β οΈ
read_line()
function it has abnormality. This function stops reading the input after The user presses the Enter/Return key. Therefore, this function also records the newline character (\n
) and a subsequent newline that is stored in the mutable string variable you passed.
So please, either keep this late newline in mind when dealing with it or remove it.
A primer on error handling in Rust
Finally, there is a file expect()
Works at the end of this series. Let’s detour a bit to understand why this function is called.
the read_line()
The function returns an Enum called Result
. I’ll get into Enums in Rust later but I know Enums is very powerful in Rust. this Result
Enum returns a value that notifies the programmer if an error occurred while reading user input.
the expect()
function take this Result
Enumerate and check whether the result is good or not. If no error occurs, nothing will happen. But if an error occurs, the message you sent ("Unable to read user input."
) to stderr f The program will exit.
π
All of the new concepts I touched upon will be covered briefly in the new Rust series later on.
Now that you hope to understand these newer concepts, let’s add some more code to increase functionality.
Validate user input
I’ve definitely accepted user input but haven’t validated it. In the current context, validation means that the user enters some “command” We expect to deal with it. At the moment, the commands are of “two classes”.
The first category of the order that the user can enter is the name of the fruit that the user wants to buy. The second command indicates that the user wants to terminate the program.
So our job now is to make sure that the input from the user is no different from Orders accepted.
use std::io;
fn main() {
println!("Welcome to the fruit mart!");
println!("Plase select a fruit to buy.\n");
println!("Available fruits to buy: Apple, Banana, Orange, Mango, Grapes");
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
// get user input
let mut user_input = String::new();
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
// validate user input
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
user_input = user_input.trim().to_lowercase();
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
}
To make validation easier, I created an array of string segments called valid_inputs
(on line 17). This matrix contains the names of all fruits available for purchase, along with slices of the string q
And quit
To allow the user to express if they would like to quit smoking.
The user may not know how to expect the input to be. User can type “Apple”, “apple” or “APPLE” to tell that they intend to buy apples. Our job is to do it right.
On line 18, I cut the late newline from user_input
String by calling trim()
working on it. And to deal with the previous problem, I converted all the characters to lowercase using to_lowercase()
A function such that “apple”, “apple” and “APPLE” all end as “apple”.
Now on line 19 I have created a mutable boolean variable called input_error
with the initial value of true
. Later on line 20 I create a file for
A loop that iterates through all elements (string segments) in a file valid_inputs
An array and stores the repeated pattern inside a file input
Factor.
Inside the loop, I check if the user input is equal to one of the valid strings, and if it is, I set a value for input_error
Boolean to false
and exit the for loop.
Handling invalid entries
Now it’s time to deal with the invalid entries. This can be done by moving some code inside an infinite loop and continuous Said infinite loop if user gives invalid input.
use std::io;
fn main() {
println!("Welcome to the fruit mart!");
println!("Plase select a fruit to buy.\n");
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
'mart: loop {
let mut user_input = String::new();
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
// get user input
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
user_input = user_input.trim().to_lowercase();
// validate user input
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
// handle invalid input
if input_error {
println!("ERROR: please enter a valid input");
continue 'mart;
}
}
}
Here, I’ve moved some of the code inside the loop and refactored the code a bit to better handle this loop introduction. Inside the loop, on line 31, i continue
the mart
loop if the user entered an invalid string.
Respond to user input
Now that everything else is dealt with, it’s time to actually write the code about buying fruit from the fruit market and quitting when the user wishes.
Since you also know which fruit the user has chosen, let’s ask how much they intend to buy and inform them of the quantity input format.
use std::io;
fn main() {
println!("Welcome to the fruit mart!");
println!("Plase select a fruit to buy.\n");
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
'mart: loop {
let mut user_input = String::new();
let mut quantity = String::new();
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
// get user input
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
user_input = user_input.trim().to_lowercase();
// validate user input
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
// handle invalid input
if input_error {
println!("ERROR: please enter a valid input");
continue 'mart;
}
// quit if user wants to
if user_input == "q" || user_input == "quit" {
break 'mart;
}
// get quantity
println!(
"\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
(Quantity of 1Kg 500g should be entered as '1.5'.)",
user_input
);
io::stdin()
.read_line(&mut quantity)
.expect("Unable to read user input.");
}
}
On line 11 I declare another mutable variable with an empty string and on line 48 I accept input from the user, but this time the amount of said fruit the user intends to buy.
Quantitative analysis
I just added code that takes a quantity in a known format, but that data is stored as a string. I need to extract the buoy from that. Lucky for us, this can be done using parse()
road.
like read_line()
road parse()
return method Result
enumeration. The reason is that parse()
return method Result
Enum can be easily understood by what we are trying to achieve.
I’m accepting a string of users and trying to convert it to a decimal. A decimal number with two possible values ββin it. One is the floating point itself and the second is a decimal number.
While a string can contain alphanumeric characters, float does not contain alphanumeric characters. So, if the user enters something last Manal [optional] Floating point and decimal number(s), the parse()
The function will return false.
Hence, this error must also be addressed. We will use a file expect()
function to handle this.
use std::io;
fn main() {
println!("Welcome to the fruit mart!");
println!("Plase select a fruit to buy.\n");
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
'mart: loop {
let mut user_input = String::new();
let mut quantity = String::new();
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
// get user input
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
user_input = user_input.trim().to_lowercase();
// validate user input
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
// handle invalid input
if input_error {
println!("ERROR: please enter a valid input");
continue 'mart;
}
// quit if user wants to
if user_input == "q" || user_input == "quit" {
break 'mart;
}
// get quantity
println!(
"\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
(Quantity of 1Kg 500g should be entered as '1.5'.)",
user_input
);
io::stdin()
.read_line(&mut quantity)
.expect("Unable to read user input.");
let quantity: f64 = quantity
.trim()
.parse()
.expect("Please enter a valid quantity.");
}
}
As you can see, I store the parsed float in the variable quantity
By making use of variable shading. to inform parse()
The function you’re aiming to parse the string into f64
I comment out the type of the variable manually quantity
like f64
.
now , parse()
The function will parse the string and return a f64
or wrong, that expect()
job you will handle.
Price calculation + finishing touches
Now that we know what fruit the user wants to buy and how much, it’s time to do these calculations now and let the user know the results/total.
For the sake of realism, I’ll have two prices per fruit. The first price is the retail price we pay fruit sellers when we buy in small quantities. The second price of the fruit will be the wholesale price, when someone buys the fruit in bulk.
Wholesale price will be quoted if the order is more than the minimum order quantity to be considered a bulk purchase. This minimum order quantity varies for each fruit. Prices for each fruit will be in rupees per kg.
With this logic in mind, below is the program in its final form.
use std::io;
const APPLE_RETAIL_PER_KG: f64 = 60.0;
const APPLE_WHOLESALE_PER_KG: f64 = 45.0;
const BANANA_RETAIL_PER_KG: f64 = 20.0;
const BANANA_WHOLESALE_PER_KG: f64 = 15.0;
const ORANGE_RETAIL_PER_KG: f64 = 100.0;
const ORANGE_WHOLESALE_PER_KG: f64 = 80.0;
const MANGO_RETAIL_PER_KG: f64 = 60.0;
const MANGO_WHOLESALE_PER_KG: f64 = 55.0;
const GRAPES_RETAIL_PER_KG: f64 = 120.0;
const GRAPES_WHOLESALE_PER_KG: f64 = 100.0;
fn main() {
println!("Welcome to the fruit mart!");
println!("Please select a fruit to buy.\n");
let mut total: f64 = 0.0;
let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
'mart: loop {
let mut user_input = String::new();
let mut quantity = String::new();
println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
// get user input
io::stdin()
.read_line(&mut user_input)
.expect("Unable to read user input.");
user_input = user_input.trim().to_lowercase();
// validate user input
let mut input_error = true;
for input in valid_inputs {
if input == user_input {
input_error = false;
break;
}
}
// handle invalid input
if input_error {
println!("ERROR: please enter a valid input");
continue 'mart;
}
// quit if user wants to
if user_input == "q" || user_input == "quit" {
break 'mart;
}
// get quantity
println!(
"\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
(Quantity of 1Kg 500g should be entered as '1.5'.)",
user_input
);
io::stdin()
.read_line(&mut quantity)
.expect("Unable to read user input.");
let quantity: f64 = quantity
.trim()
.parse()
.expect("Please enter a valid quantity.");
total += calc_price(quantity, user_input);
}
println!("\n\nYour total is {} Rupees.", total);
}
fn calc_price(quantity: f64, fruit: String) -> f64 {
if fruit == "apple" {
price_apple(quantity)
} else if fruit == "banana" {
price_banana(quantity)
} else if fruit == "orange" {
price_orange(quantity)
} else if fruit == "mango" {
price_mango(quantity)
} else {
price_grapes(quantity)
}
}
fn price_apple(quantity: f64) -> f64 {
if quantity > 7.0 {
quantity * APPLE_WHOLESALE_PER_KG
} else {
quantity * APPLE_RETAIL_PER_KG
}
}
fn price_banana(quantity: f64) -> f64 {
if quantity > 4.0 {
quantity * BANANA_WHOLESALE_PER_KG
} else {
quantity * BANANA_RETAIL_PER_KG
}
}
fn price_orange(quantity: f64) -> f64 {
if quantity > 3.5 {
quantity * ORANGE_WHOLESALE_PER_KG
} else {
quantity * ORANGE_RETAIL_PER_KG
}
}
fn price_mango(quantity: f64) -> f64 {
if quantity > 5.0 {
quantity * MANGO_WHOLESALE_PER_KG
} else {
quantity * MANGO_RETAIL_PER_KG
}
}
fn price_grapes(quantity: f64) -> f64 {
if quantity > 2.0 {
quantity * GRAPES_WHOLESALE_PER_KG
} else {
quantity * GRAPES_RETAIL_PER_KG
}
}
Compared to the previous iteration, I made a few changes…
Fruit prices may fluctuate, but for the life cycle of our software, these prices will not fluctuate. So I store retail and wholesale prices for each fruit in constants. I set these constants out main()
jobs (ie globally) because I will not calculate the prices of each fruit within main()
job. These constants are declared as f64
Because they will multiply quantity
that f64
. Recall, Rust does not have an implicit type π
After storing the name of the fruit and the quantity the user wants to buy, a file is created calc_price()
The function is called to calculate the price of the mentioned fruit in the quantity provided by the user. This function takes the name of the fruit and the quantity as its parameters and returns the price as f64
.
looking inside calc_price()
A function, this is what many people call an assembler function. It is called the cover function because it calls other functions to do their dirty laundry.
Because each fruit has a different minimum order quantity to be considered a bulk purchase, and to ensure that the code can be easily maintained in the future, the calculation of the actual price of each fruit is divided into separate functions for each individual fruit.
So, all of it calc_price()
The function is to identify which fruit has been selected and call the respective function of the selected fruit. These fruit functions accept only one argument: quantity. And these functions of fruit return price as f64
.
now, price_*()
Functions only do one thing. They check if the order quantity is greater than the minimum order quantity to be considered bulk purchase of the said fruit. If so , quantity
Multiplied by the wholesale price of the fruit per kg. Unlike that, quantity
Multiplied by the retail price of the fruit per kilogram.
Since the line containing the multiplication does not have a semicolon at the end, the function returns the resulting product.
If you look closely at the function calls to the functions for the fruit in the calc_price()
function, these function calls do not have a semicolon at the end. That is, the value returned by a file price_*()
functions will be returned by calc_price()
function of the contact.
There is only one caller calc_price()
job. This is at the end mart
A loop where the value returned from this function is what is used to increment a value total
.
Finally, when mart
The loop ends (when the user enters q
or quit
), the value stored inside the variable total
on the screen and the user is informed of the price he has to pay.
Conclusion
With this post, I used all of the previously described topics about the Rust programming language to create a simple program that still somewhat illustrates a real-world problem.
Now, the code I’ve written could definitely be written in a more idiomatic way that uses Rust’s best loved features but I haven’t covered it yet!
So stay tuned for the follow up Take Rust to the next level series Learn more Rust!
The Rust Basics series concludes here. I welcome your comments.