Follow

Follow

Introduction To Rust - Part 1

Sangam Biradar's photo
Sangam Biradar
·Dec 19, 2022·

55 min read

Introduction To Rust - Part 1

Table of contents

What is Rust?

Rust is a language empowering everyone to build reliable and efficient software. rust created by Graydon Hoare and many others around 2006 while Hoare was working at Mozilla Research. It gained enough interest and users that by 2010 Mozilla had sponsored the development efforts

https://survey.stackoverflow.co/2022/#technology-most-loved-dreaded-and-wanted

StackOverflow 2022 - it's the seventh year as the most loved programing language with 87% of developers saying they want to continue using it.

the language is syntactically similar to C, so you'll find things like for loops, semicolon-terminated statements and curly braces denoting block structures, Rust can guarantee memory safety through the use of a borrow checker that tracks which part of a program has safe access to a different part of memory. This track does not come at the expense of performance, though. Rust programs compile to native binaries and often match or beat the speed of programs written in c or c++ for this reason, Rust is often described as a system programing language that has been designed for performance and safety.

To start, You'll need to install Rust

one of the easy ways to install, upgrade, and manage Rust using

the rustup tool .it works equally well on Windows, Linux or macOS.

 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

if you have already installed rustup , you might want to run rustup update to get the latest version of the language and tools, as Rust updates in about six weeks. execute rustup doc to read volumes of documentation. you can check your version of rustc compiler with the following command.

rustc --version 
rustc 1.68.0-nightly (b70baa4f9 2022-12-14)

Getting Started with "Hello, World! "

It's universal truth everybody starts their journey with the hello world program.

create hello.rs file with the following content :

fn main(){
     println!("hello,world!");
}
  • functions are defined using fn the name of this function in main.

  • println is a micro and will print txt to STDOUT.

  • the body of the program is enclosed in curly braces.

rust will automatically start in the main function. function parameter appears inside the parentheses that follow the name of the function. because there is no argument lists in main() . the function takes no arguments.

to run this program, you must first use rust compiler, rustc to compile the code.

$ rustc hello.rs

on macOS, you can use the file command to see what kind of file this is

rustlabs file hello
hello: Mach-O 64-bit executable arm64

you can execute your program like this on mac

rustlabs ./hello
hello,world!

The dot(.) indicates the current directory

you can execute your program like this if your using windows

rustlabs ./hello.exe
hello,world!

🍾 cheers!

quiz
  1. What is the keyword for declaring a function?
  • fun

  • function

  • func

  • fn

  1. What is the output of the following code?

fn main() {
    println!("Hello World!")
    println!("Hello");
}

a) Hello World! Hello b) error hint:- A semicolon can be omitted at the end of the last statement only.

Organizing a Rust Project :

for any project, we all write multiple source files and execute them as a single binary. let's remove our existing hello binary rm hello

let's create a parent directory

$ mkdir -p hello/src

now move the hello.rssource file into hello/src using the mv command

$ mv hello 
$ rustc src/hello.rs

check the content of the directory using tree command :

➜  rustlabs tree
.
└── hello
    └── src
        └── hello.rs

Basic Formatting

A single placeholder is used when it is required to print a single value.

fn main() {
    println!("Number: {}", 1);
}

We can use multiple placeholders within the println!() macro. The number of placeholders should be equal to the number of values to be printed.

fn main() {
    println!("{} is a {} Community", "Join", "CloudNativeFolks");
}

Positional arguments specify the positions of the values in a sentence.

fn main() {
    println!("Enhance your coding skills from {0} Track. {0} by {1}", "Rustlabs ", " CloudNativeFolks");
}

A placeholder can take a named argument and assign it a value explicitly. It does this by specifying a name within the placeholder and assigning that name the value to be inserted in the string.

fn main() {
    println!("{workshop} by {Community} ", Workshop = "Rust", Commmunity = "CloudNativeFolks");
}

If we want to convert the value to binary, hexadecimal, or octal write:

{:b},{:x},{:o}

In the placeholder for binary, hexadecimal, or octal respectively and in the value specify the number. You can use all three of them or one of them in a single expression.

fn main() {
    println!("Number : 10 \nBinary:{:b} Hexadecimal:{:x} Octal:{:o}", 10, 10, 10);
}

If we want to convert the value to binary, hexadecimal, or octal write:

{:b},{:x},{:o}

In the placeholder for binary, hexadecimal, or octal respectively and in the value specify the number. You can use all three of them or one of them in a single expression.

fn main() {
    println!("Number : 10 \nBinary:{:b} Hexadecimal:{:x} Octal:{:o}", 10, 10, 10);
}

We can perform basic math and the placeholder gets replaced with the result.

fn main() {
    println!("{} + {} = {}",10, 10, 10 + 10);
}

Placeholder for debug trait

fn main() {
    println!("{:?}", ("This is a Rustlabs", 101));
}

Printing style

The table below summarizes the macros used to print in Rust.

macroPrinting Style
print!()prints strings to console
println!()same as print!() but also appends a new line character at end of the string
eprint!()prints anything within the parentheses as an error
eprintln!()same as eprint!() but also appends a new line character at the end

Let’s discuss each of the macros in detail.

The print!() the macro simply prints the output to the console. The following example prints “RustLabs by CloudNativeFolks Community ” in one line

fn main() {
    print!("RustLab ");
    print!(" by CloudNativeFolks Community ");
}

println!()

The println!() macro appends a new line at the end of the string.

The following example prints “RustLabs” on one line and “by CloudNativeFolks Community” on the new line.

fn main() {
    println!("RustLabs");
    println!("by CloudNativeFolks Community");
}

eprint!()

The eprint!() macro displays the output as an error.

The following example prints “RustLabs” and “by CloudNativeFolks Community” on the same line but as an error.

fn main() {
    eprint!("RustLabs);
    eprint!("by CloudNativeFolks Community");
}

eprintln!()

The eprint!() macro displays the output as an error and appends a new line(\n) at the end of it.

The following example prints “RustLabs” as an error and appends a new line to it. Then prints “by CloudNativeFolks Community” and appends a new line.

fn main() {
    eprintln!("RustLabs");
    eprintln!("by CloudNativeFolks Community");
}

📝Note: eprint!() and eprintln!() come in handy when we want to indicate to the user that an error condition has occurred.

Types of Comments in Rust

Rust has two types of comments that are commonly used:

Line Comments //

The comments followed by a double forward slash, //, are known as line comments.

This is the most recommended style of comment for commenting a single line.

// Writing a Rust program
fn main() {
    //The line comment is the recommended comment style
    println!("This is a line comment!"); // print hello World to the screen
}

Block Comments /*...*/

Text enclosed within /*...*/ is known as a block comment.

This is used for temporarily disabling a large chunk of code.

Doc Comments

The comments followed by /// or //! are known as doc comments. Let’s see the difference between the two sets of doc comments.

Outer Doc Comments ///

Outer doc comments are written outside the block of code.

This type of comment supports markdown notation. This is used to generate docs referring to the following item.

Inner Doc Comments //!

Inner doc comments are written inside the block of code.

This comment style is used to generate docs referring to the item within a block of code.

/// This is a Doc comment outside the function
/// Generate docs for the following item.
/// This shows my code outside a module or a function
fn main() {
    //! This a doc comment that is inside the function   
    //! This comment shows my code inside a module or a function  
    //! Generate docs for the enclosing item
    println!("{} can support {} notation","Doc comment","markdown");
}

Variables

A variable is like a storage box paired with an associated name which contains data. The associated name is the identifier and the data that goes inside the variable is the value. They are immutable by default, meaning, you cannot reassign value to them.

Create a Variable

To create a variable, use the let binding followed by the variable name.

  • What is binding?
    Rust refers to declarations as bindings as they bind a name at the time of creation. let is a kind of declaration statement.

  • Naming Convention: By convention, you would write a variable name in a snake_case i.e., - All letters should be lower case. - All words should be separated using an underscore ( _ ).

Initialize a Variable

A variable can be initialized by assigning a value to it when it is declared. The value is said to be bound to that variable.

Note: It’s possible to declare the variable first and assign it a value later. However, it is not recommended to do this as it may lead to the use of uninitialized variables.

The example below declares a variable, language, and initializes it with a value, Rust, and then displays the value of a said variable:

fn main() {
    let language = "Rust"; // define a variable
    println!("Language: {}", language); // print the variable
}

What if You Want to Make a Variable Mutable?

At the beginning of this lesson, it was mentioned that a variable is immutable until you want to make a change in the variable, then it can be made mutable. To make a variable mutable, write let followed by the mut keyword and the variable name:

fn main() {
    let mut language = "Rust"; // define a mutable variable
    println!("Language: {}", language); // print the variable
    language = "Java"; // update the variable
    println!("Language: {}", language); // print the updated value of variable
}

Assigning Multiple Variables

It is possible to assign multiple variables in one statement.

fn main() {
    let (language,community) =("Rust","CloudNativeFolks"); // assign multiple values
    println!("This is a {} labs by {}.", language ,CloudNativefolks ); // print the value
}

Note: If a variable is kept un-assigned or unused, you’ll get a warning. To remove such a warning write the expression #[allow(unused_variables, unused_mut)] at the start of the program code. However, it’s not a good practice to keep unassigned/unused variables.

Scope and Shadowing

The scope of a variable refers to the visibility of a variable, or , which parts of a program can access that variable.
It all depends on where this variable is being declared. If it is declared inside any curly braces {}, i.e., a block of code, its scope is restricted within the braces, otherwise, the scope is global.
Types of Variables
There are two types of variables in terms of scope.
- Local Variable
A variable that is within a block of code, { }, that cannot be accessed outside that block is a local variable. After the closing curly brace, } , the variable is freed and memory for the variable is deallocated.
- Global Variable
The variables that are declared outside any block of code and can be accessed within any subsequent blocks are known as global variables.
The variables declared using the const keyword can be declared in local as well as a global scope.

Note: The following code gives an error, ❌, since the variable created inside the inner block of code has been accessed outside its scope.

fn main() {
  let outer_variable = 112;
  { // start of code block
        let inner_variable = 213;
        println!("block variable inner: {}", inner_variable);
        println!("block variable outer: {}", outer_variable);
  } // end of code block
    println!("inner variable: {}", inner_variable); // use of inner_variable outside scope
}

output

rror[E0425]: cannot find value `inner_variable` in this scope
 --> main.rs:8:36
  |
8 |     println!("inner variable: {}", inner_variable); // use of inner_variable outside scope
  |                                    ^^^^^^^^^^^^^^ help: a local variable with a similar name exists: `outer_variable`

error: aborting due to previous error

To fix this error, the variable declaration can be moved outside the inner block of code. That way, the scope of the variable spans the entire main() function. This is shown below:

fn main() {
  let outer_variable = 112;
  let inner_variable = 213;
  { // start of code block
        println!("block variable inner: {}", inner_variable);
        println!("block variable outer: {}", outer_variable);
  } // end of code block
    println!("inner variable: {}", inner_variable);
  }

output

block variable inner: 213
block variable outer: 112
inner variable: 213

Shadowing

Variable shadowing is a technique in which a variable declared within a certain scope has the same name as a variable declared in an outer scope. This is also known as masking. This outer variable is said to be shadowed by the inner variable, while the inner variable is said to mask the outer variable.

The following code explains the concept.

fn main() {
  let outer_variable = 112;
  { // start of code block
        let inner_variable = 213;
        println!("block variable: {}", inner_variable);
        let outer_variable = 117;
        println!("block variable outer: {}", outer_variable);
  } // end of code block
    println!("outer variable: {}", outer_variable);
  }

output


block variable: 213
block variable outer: 117
outer variable: 112

What Are Data Types?
- Data Type
Rust is a statically typed language, meaning, it must know the type of all variables at compile time.

How to Define a Type in Rust?
We can define a variable in rust in two different ways:
- Implicit Definition
Unlike other languages like C++ and Java, Rust can infer the type from the type of value assigned to a variable.

The general syntax is :

let variable name = value ;

Explicit Definition

Explicitly tells the compiler about the type of variable.
The general syntax is:

let variable name : data type = value ;

Primitive Types
Rust has a couple of types that are considered primitive. That means they are built-in into the language. There are different data types used for different purposes.
The following illustration shows the different primitive data types in Rust:

**
Scalar Type**

They store a single value.

- Below is the list of scalar types:

- Integers

- Floats

- Boolean

- Character

Compound Type
They group multiple values in one variable. Below is the list of compound types:-

- Array

- Tuple

Numeric Types - Integers and Floats

  • Integers

    Variables of Integer data type hold whole number values. There are two subtypes of integer data type in Rust, based on the number of bits occupied by a variable in memory.

  • Fixed Size Types

    The fixed integer types have a specific number of bits in their notation. This notation is a combination of a letter and a number. The former denotes the category of the integer, whether it is, unsigned or signed, and the latter denotes the size of an integer, i.e., 8, 16, 32, 64.

  • Below is the list of fixed-length integer types:

    • i8: The 8-bit signed integer type.

    • i16: The 16-bit signed integer type.

    • i32: The 32-bit signed integer type.

    • i64: The 64-bit signed integer type.

    • u8: The 8-bit unsigned integer type.

    • u16: The 16-bit unsigned integer type.

    • u32: The 32-bit unsigned integer type.

    • u64: The 64-bit unsigned integer type.

Variable Size Types

The integer type in which the particular size depends on the underlying machine architecture.

💡 Why are there so many types of integers and how do you pick a data type?

The choice depends on what values a variable is expected to hold. So, a programmer should pick a data type that is not so small that the data is lost. Nor should they pick a data type that is so big that it wastes memory.

isize: The pointer-sized signed integer type. usize: The pointer-sized unsigned integer type

Example
The code below defines an integer type both explicitly and implicitly:
- Explicit Definition
The following code explicitly defines the integer variables using the integer type fixed or variable):

fn main() {
    //explicitly define an integer
    let a:i32 = 24;
    let b:u64 = 23;
    let c:usize = 26;
    let d:isize = 29;
    //print the values
    println!("a: {}", a);
    println!("b: {}", b);
    println!("c: {}", c);
    println!("d: {}", d);

}

output

a: 24
b: 23
c: 26
d: 29

Implicit Definition

The following code implicitly defines the integer type of the variable by assigning an integer value to the variable.

fn main() {
    //explicitly define an integer
    let a = 21; 
    let b = 1;
    let c = 54;
    let d = 343434;
    //print the variable
    println!("a: {}", a);
    println!("b: {}", b);
    println!("c: {}", c);
    println!("d: {}", d);

}

output

a: 21
b: 1
c: 54
d: 343434

Floating Point

Floating-point numbers refer to numbers with a fractional part.
The representation of floating-point numbers in a computer’s memory is such that the precision with which a number is stored in memory depends on the number of bits used for storing the variable.
In this respect, there are two subtypes: single-precision f32 and double-precision f64 floating-point, with the latter having more bits to store the number.

  • f32: The 32-bit floating point type.

  • f64: The 64-bit floating point type.

Example

The code below defines a floating-point number both explicitly and implicitly:

  • Explicit Definition

The following code explicitly defines the float variable using the float type (f32 or f64)

fn main() {
    //explicitly define a float type
    let f1:f32 = 32.9;
    let f2:f64 = 6789.89;
    println!("f1: {}", f1);
    println!("f2: {}", f2);
}

output

f1: 32.9
f2: 6789.89

Implicit Definition:

The following code implicitly defines the float type of the variable by assigning a floating-point value to the variable:

fn main() {
    //implicitly define a float type
    let pi = 3.14;
    let e = 2.17828;
    println!("pi: {}", pi);
    println!("e: {}", e);
}

output

pi: 3.14
e: 2.17828

Boolean

The boolean variable can take a value of either true or false. The following code explains how to define a boolean variable in three different ways:

  • Explicit Definition The following code explicitly defines the variable using the bool keyword:

    n main() {
        //explicitly define a bool
        let is_bool:bool = true;
        println!("explicitly_defined: {}", is_bool);
    }
    

    output rust explicitly_defined: true

    • Implicit Definition

      The following code implicitly defines the boolean type of a variable by assigning the value true or false to the variable.

      fn main() {
          // assign a boolean value
          let a = true;
          let b = false;
          println!("a: {}", a);
          println!("b: {}", b);
      }
      

Result of an Expression
The result of an expression that evaluates to either true or false (for example a comparison of two values) can be assigned to an implicit boolean variable

fn main() {
    // get a value from an expression
    let c = 10 > 2;
    println!("c: {}", c);
}

Character and String

  • Character

The variable is used to store a single character value, such as a single digit or a single alphabet. The value assigned to a char variable is enclosed in a single quote('').

Note: Unlike some other languages, a character in Rust takes up 4 bytes rather than a single byte. It does so because it can store a lot more than just an ASCII value like emojis, Korean, Chinese, and Japanese characters.

Example

The code below defines a character both explicitly and implicitly:

  • Explicit Definition

The following code explicitly defines the variable using the char keyword:

fn main() {
    // explicitly define 
    let char_1:char = 'e';
    println!("character1: {}", char_1); 
}

Output

character1: e

Implicit Definition

The following code implicitly defines the character type of the variable by assigning the single value enclosed within single quotes to them.

fn main() { 
    // implicitly define
    let char_2 = 'a';
    let char_3 = 'b';
    println!("character2: {}", char_2);
    println!("character3: {}", char_3);
}

Output

character2: a
character3: b

String

A string is any sequence of characters enclosed within double quotes (" ").

Example

The code below defines a string both explicitly and implicitly:

  • Explicit Definition

The following code explicitly defines the variable using the &str keyword:

fn main() {
    // explicitly define 
    let str_1:&str = "Rust Programming";
    println!("String 1: {}", str_1); 
}

Output

String 1: Rust Programming

Implicit Definition

The following code implicitly defines the string type of the variable by assigning the single value enclosed within double quotes to them.


fn main() { 
    // implicitly define
    let str_2 = "Rust Programming";
    println!("String 2: {}", str_2);
}

output:-

String 2: Rust Programming

Arrays

  • What Is an Array?

An array is a homogenous sequence of elements. Being a compound type, it is used when the collection of values of the same type is to be stored in a single variable. In Rust, an array can only be of a fixed length. Like all other languages, each element in the array is assigned an index. By default, the first element is always at index 0.

Note: By default, arrays are immutable.

Define an Array

To define an array in Rust, we have to define the type and size of the array. To initialize an array, the array elements are enclosed in square brackets []. The following illustration explains the concept:

#[allow(unused_variables, unused_mut)]
fn main() {
   //define an array of size 4
   let arr:[i32;4] = [1, 2, 3, 4]; 
   // initialize an array of size 4 with 0
   let arr1 = [0 ; 4]; 
}
  • The array arr declaration on line 4 declares an array with elements 1,2,3,4.

  • The array arr1 declaration on line 6 implicitly determines the data type (integer) from the value 0 and 4, is the size of the array. So, this becomes an array consisting of 4 zeros.

Access an Element of an Array
- Any value of the array can be accessed by writing the array name followed by the index number enclosed within square brackets [ ].

fn main() {
   //define an array of size 4
   let arr:[i32;4] = [1, 2, 3, 4]; 
   //print the first element of array
   println!("The first value of array is {}", arr[0]);
   // initialize an array of size 4 with 0
   let arr1 = [0; 4]; 
   //print the first element of array
   println!("The first value of array is {}", arr1[0]);
}

output

The first value of array is 1
The first value of array is 0

How to Make an Array Mutable? Just like a variable becomes mutable by adding the mut keyword after let, the same goes for an array.

fn main() {
    //define a mutable array of size 4
    let mut arr:[i32;4] = [1, 2, 3, 4]; 
    println!("The value of array at index 1: {}", arr[1]);
    arr[1] = 9;
    println!("The value of array at index 1: {}", arr[1]);
}

output

The value of array at index 1: 2
The value of array at index 1: 9

Print the Array The whole array can be traversed using a loop or the debug trait.

fn main() {
    //define an array of size 4
    let arr:[i32;4] = [1, 2, 3, 4]; 
    //Using debug trait
    println!("\nPrint using a debug trait");
    println!("Array: {:?}", arr);
}

output

Print using a debug trait
Array: [1, 2, 3, 4]

Get the Length of the Array To access the length of the array, use the built-in function len.

fn main() {
    //define an array of size 4
    let arr:[i32;4] = [1, 2, 3, 4]; 
    // print the length of array
    println!("Length of array: {}", arr.len());
}

Get Slice
Slice is basically a portion of an array. It lets you refer to a subset of a contiguous memory location. But unlike an array, the size of the slice is not known at compile time.

  • Syntax

    • A slice is a two-word object, the first word is a data pointer and the second word is a slice length.

    • Data pointer is a programming language object that points to the memory location of the data, i.e., it stores the memory address of the data.

    • To declare an array slice, we need to specify the name of the source array and the range of elements to be included in the slice. Note: If the range of elements is not specified, it will consider the whole array as a slice.

 fn main() {
    //define an array of size 4
    let arr:[i32;4] = [1, 2, 3, 4]; 
    //define the slice
    let slice_array1:&[i32] = &arr;
    let slice_array2:&[i32] = &arr[0..2];
    // print the slice of an array
    println!("Slice of an array: {:?}", slice_array1);
    println!("Slice of an array: {:?}", slice_array2);
}

output

Slice of an array: [1, 2, 3, 4]
Slice of an array: [1, 2]

What are Tuples?

  • Tuples are heterogeneous sequences of elements, meaning, each element in a tuple can have a different data type. Just like arrays, tuples are of a fixed length.

  • Define a Tuple

A tuple can be defined by writing let followed by the name of the tuple and then enclosing the values within the parenthesis.

  • Syntax 1

    • The syntax below defines a tuple without specifying the type. However, the compiler can infer the type.

  • Syntax2

    • The syntax below defines a tuple by specifying the type.

Example

The following illustration explains the concept:

#[allow(unused_variables, unused_mut)]
fn main() {
    //define a tuple
    let person_data = ("Alex", 48, "35kg", "6ft");
    // define a tuple with type annotated
    let person_data : (&str, i32, &str, &str) = ("Alex", 48, "35kg", "6ft");
}
  • Access the Value of the Tuple

  • Unlike array which uses [] for accessing an element, the value of the tuple can be accessed using the dot operator (.).

  • tuplename.indexvalue

  • To get the individual values out of a tuple, we can use pattern matching to destructure a tuple value, like this:

    let person_data = ("Alex", 48, "35kg", "6ft");
       let (w, x, y, z) = person_data;
    
    fn main() {
       //define a tuple
       let person_data = ("Alex", 48, "35kg", "6ft");
       // access value of a tuple
       println!("The value of the tuple at index 0 and index 1 are {} {}",person_data.0,person_data.1);
    
       //define a tuple
       let person_data = ("Alex", 48, "35kg", "6ft");
       // get individual values out of tuple
       let (w ,x, y, z) = person_data;
       //print values
       println!("Name : {}",w);
       println!("Age : {}",x);
       println!("Weight : {}",y);
       println!("Height : {}",z);
    }
    

    output

    The value of the tuple at index 0 and index 1 are Alex 48
    Name : Alex
    Age : 48
    Weight : 35kg
    Height : 6ft
    

How to Make a Tuple Mutable?

Just like a variable becomes mutable by adding the mut keyword after let, the same goes for a tuple.

fn main() {
    //define a tuple
    let mut person_data = ("Alex", 48, "35kg", "6ft");
    //print the value of tuple
    println!("The value of the tuple at index 0 and index 1 are {} {}", person_data.0, person_data.1);
    //modify the value at index 0
    person_data.0 = "John";
    //print the modified value
    println!("The value of the tuple at index 0 and index 1 are {} {}", person_data.0, person_data.1);
}

output:-

The value of the tuple at index 0 and index 1 are Alex 48
The value of the tuple at index 0 and index 1 are John 48

Print the Tuple

The whole tuple can be traversed using the debug trait.

fn main() {
    //define a tuple
    let person_data = ("Alex", 48, "35kg", "6ft");
    //print the value of tuple
    println!("Tuple - Person Data : {:?}",person_data);
}

output:-

Tuple - Person Data : ("Alex", 48, "35kg", "6ft")

What Are Constant Variables?

  • Constant variables are declared constant throughout the program scope, meaning, their value cannot be modified. They can be defined in the global and local scopes.
  • They are declared using the const keyword followed by the name of the variable, colon (:), and then the data type of the variable.

  • Naming Convention: By convention, you write a constant variable name in a SCREAMING_SNAKE_CASE, i.e.,

    • All letters should be UPPER case.

    • All words should be separated using an underscore ( _ )

      The following example defines two const variables:

      • ID_1 in global scope

      • ID_2 in local scope

    ```rust
      const ID_1: i32 = 4; // define a global constant variable
    fn main() {
       const ID_2: u32 = 3; // define a local constant variable
       println!("ID:{}", ID_1); // print the global constant variable
       println!("ID:{}", ID_2); // print the local constant variable
    }
    ```
    output:

    ```rust
    ID:4
    ID:3
    ```

Difference Between const and let Variables

There are many differences between const and let variables.

  • Declaration
  • Constant variables are declared using the const keyword unlike let variables.
  • Scope
  • const variables are declared in global and local scope unlike let variables that are declared only in the local scope.
  • Mutability
    • const variable cannot be mutable unlike let which can be made mutable using mut keyword.
  • Data Type *Unlike let variables, it is mandatory to define the data type of const variables.
  • Set Value at Run-time
  • The value of const variable can only be set before running the program whereas the let variable can store the result at runtime.
  • Shadowing
  • Unlike let variables, const variables cannot be shadowed.

Introduction to Operators

Operators
  • An operator is a symbol that takes one or more values and outputs another. It tells the compiler to perform some sort of operation.
Types of Operators
  • Different operators are available in Rust for performing different operations. Based on the number of operands, the operators can be categorized into binary and unary operators:
Unary Operators
  • The operators that act upon a single operand are unary operators.

  • Types

    • Borrow Expression

    • Dereference Expression

    • Negation Expression

    • Logical Negation Expression

Binary Operators
*   The operators that deal with two operands are binary operators.

Arithmetic Operators
  • What Are Arithmetic Operators?

    • Arithmetic operators are used to perform arithmetic operations.
  • Types The table below summarizes the arithmetic operations in Rust.

Types

The table below summarizes the arithmetic operations in Rust.

operatoroperationexplanation
operand1 + operand2additionadd operand1 and operand2
operand1 - operand2subtractionsubtract operand2 from operand1
operand1 / operand2dividedivide operand1 by operand2
operand1 * operand2multiplicationmultiply operand1 with operand2
operand1 % operand2modulusget reminder of operand1 by dividing with operand2

The following example shows the use of arithmetic operators in a program:


fn main() {
    let a = 4;
    let b = 3;

    println!("Operand 1:{}, Operand 2:{}", a , b);
    println!("Addition:{}", a + b);
    println!("Subtraction:{}", a - b);
    println!("Multiplication:{}", a * b);
    println!("Division:{}", a / b);
    println!("Modulus:{}", a % b);
}

output

Operand 1:4, Operand 2:3
Addition:7
Subtraction:1
Multiplication:12
Division:1
Modulus:1
Logical Operators
What Are Logical Operators?

Logical operators operate on true / false values

Types

The following table summarizes the types and functions of the logical operators:

operatoroperationexplanation
operand1 && operand2ANDEvaluates to true if operand 1 and Operand 2 both evaluates to be true
operand1operand2
! operand1NOTnegates the value of single operand

The logical AND and OR are known as Lazy Boolean expressions because the left-hand side operand of the operator is first evaluated. If it is false, there is no need to evaluate the right-hand side operand in case of AND. If it is true, there is no need to evaluate the right-hand side operand in case of OR.

The following example shows the use of logical operators in a program:

fn main() {
  let a = true;
  let b = false;
  println!("Operand 1:{}, Operand 2:{}", a , b);
  println!("AND:{}", a && b);
  println!("OR:{}", a || b);
  println!("NOT:{}", ! a);
}

output

Operand 1:true, Operand 2:false
AND:false
OR:true
NOT:false
Comparison Operators
What Are Comparison Operators?

Comparison Operators are used for comparing the values of two operands.

Types

Below is the list of comparison operators in Rust.

operatoroperationexplanation
operand1 > operand2greater thenEvaluates to true if operand 1 is greater then Operand 2
operand1 < operand2lesser thenEvaluates to true if operand 1 is lesser then Operand 2
operand1 <= operand2less then equal toEvaluates to true if operand 1 is lesser or equal to the Operand 2
operand1 >= operand2greater then equal toEvaluates to true if operand 1 is greater or equal to the Operand 2
operand1 == operand2equal toEvaluates to true if operand 1 equal to the Operand 2
operand1 != operand2Not equal toEvaluates to true if operand 1 not equal to the Operand 2

The following example shows the use of comparison operators in a program:

fn main() {
    let a = 2;
    let b = 3;
    println!("Operand 1:{}, Operand 2:{}", a , b);
    println!("a > b:{}", a > b);
    println!("a < b:{}", a < b);
    println!("a >= b:{}", a >= b);
    println!("a <= b:{}", a <= b);
    println!("a == b:{}", a == b);
    println!("a != b:{}", a != b);
}

output

Operand 1:2, Operand 2:3
a > b:false
a < b:true
a >= b:false
a <= b:true
a == b:false
a != b:true
Bitwise Operators
  • What Are Bitwise Operators? Bitwise operators deal with the binary representation of the operands.

Types

The table below summarizes the types of bitwise operators in Rust.

operatoroperationexplanation
operand1 & operand2ANDbitwise AND operand1 and operand2
operand1operand2OR
operand1 ^ operand2XORbitwise XOR operand1 and operand2
! operand1NOTInverse the bit of operand
<< operandLeft shiftmoves all the operand1 to the left by the number of places specified in the operand 2
new bits filled with zeros . shifting a value left by one position is equivalent to multiplying it by 2 ,
Shifting to positions is equivalent to multiplying it by 4 and so on
\>> operandRight Shiftmoves all the operand1 to the right by the number of places specified in the operand 2
new bits filled with zeros . shifting a value right by one position is equivalent to multiplying it by 2 ,
Shifting to positions is equivalent to multiplying it by 4 and so on

📝 Note: Right shift >> is same as arithmetic right shift on signed integer types, logical right shift on unsigned integer types.

  • Example

The example below shows the bitwise AND, OR, XOR, Left Shift, and Right Shift operations.

The following example shows the use of bitwise operators in a program:

fn main() {
  let a = 5;
  let b = 6;
  println!("Operand 1: {}, Operand 2: {}", a , b);
  println!("AND: {}", a & b);
  println!("OR: {}", a | b);
  println!("XOR: {}", a ^ b);
  println!("NOT a: {}", !a);
  println!("Left shift: {}", a << 2);
  println!("Right shift: {}", a >> 1);

}

output

Operand 1: 5, Operand 2: 6
AND: 4
OR: 7
XOR: 3
NOT a: -6
Left shift: 20
Right shift: 2

Assignment and Compound Assignment Operators

  • Assignment Operator The assignment operator is used to save a value in the variable.

Type

Rust has only one assignment operator, = . The following table defines the function of the operator.

operatoroperationexplanationexample
operand1 = operand2assign a valueassign a value of operand 2 to operand 1a = 1
b = a

The following example demonstrates the use of some of the assignment operator in a program:

fn main() {
   let a = 2;
   let b = a;
   println!("b = a");
   println!("Value of a:{}", a);
   println!("Value of b:{}", b);
}

output:-

b = a
Value of a:2
Value of b:2
Compound Assignment Operator

The compound assignment operator is used to perform an operation and then assign that value to the operand.

Types

The following table summarizes the types of compound assignment operators

operatoroperationexplanation
operand1 += operand2

operand1 -= operand2 | add a value and assign

subtract a value and assign | add left-hand side to right-hand side and then save updated value to left operand

add right-hand side to right-hand side and then save updated value to left operand | | operand1 /= operand2

operand1 *= operand2 | divide a value and assign

multiple a value and assign | divide left-hand side to right-hand side and then save updated value to left operand

multiply left-hand side to right-hand side and then save updated value to left operand | | operand1 %= operand2 | modulus and assign | take modulus of the left-hand side with right-hand operand and then save updated value to left operand | | operand1 &= operand2 | Bitwise AND and assign | Bitwise AND of the left-hand side with right-hand operand and then save updated value to left operand | | operand1 |= operand2 | Bitwise OR and assign | Bitwise OR of the left-hand side with right-hand operand and then save updated value to left operand | | operand1 ^= operand2 | Bitwise XOR and assign | Bitwise XOR of the left-hand side with right-hand operand and then save updated value to left operand | | <<= operand1 | left sift and assign | left shift the operand x times then save updated value to operand | | >>= operand1 | right shift and assign | right shift the operand x times then save updated value to operand |

The following example demonstrates the use of some of these operators in a program:


fn main() {
    //define a mutable variable
    let mut a = 2;
    println!("a:{}", a);
    a += 1;
    println!("a+=1:{}", a);
    println!("a:{}", a);
    a -= 1;
    println!("a-=1:{}", a);
    println!("a:{}", a);
    a /= 1;
    println!("a/=1:{}", a);
    println!("a:{}", a);
    a *= 3;
    println!("a/=3:{}", a);
}

output:

a:2
a+=1:3
a:3
a-=1:2
a:2
a/=1:2
a:2
a/=3:6

Type Casting Operator

What Is Type Casting? Type casting is when you convert the data type of the variable to some other data type.

  • Type Casting in Rust In Rust, typecasting is done using the as keyword followed by the desired data type of the variable or value.

The following example demonstrates the use of type casting operator in a program:


fn main() {
    let a = 15;
    let b = (a as f64) / 2.0; 
    println!("a: {}", a);
    println!("b: {}", b);
}

output

a: 15
b: 7.5

📝 What data types can be type casted?

  • Integer can be type casted to floating-point and vice versa.

  • Integer can be typecasted to String

📝What data types cannot be type casted?

  • String (&str) or character cannot be type casted to the data type of type integer or float.

  • Character cannot be type casted to String type and vice versa

The following code gives an error, ❌, because of the invalid type casting operation:

fn main() {
    let a: char = 'r' ; // cannot be type casted
    let b = a as &str ; 
    println!("a: {}", a);
    println!("b: {}", b);
}

Borrowing and Dereferencing Operators

Borrowing Operator

Borrowing means to reference the original data binding or to share the data.References are just like pointers in C.

Two variables are involved in a borrowing relationship when the referenced variable holds a value that the referencing variable borrows. The referencing variable simply points to the memory location of the referenced variable.

The following illustration shows that operand 1 borrows the value of operand 2 using two types of operators:

Types

Borrowing can be of two types:

  • Shared borrowing

  • A piece of data that is shared by single or multiple variables but it cannot be altered

  • Mutable borrowing

  • A piece of data that is shared and altered by a single variable (but the data is inaccessible to other variables at that time)

  • The following table summarizes the function of these two types.

operatoroperationexplanation
Operand1 = & Operand2shared borrowoperand 1 can read data of another operand 2
Operand1 = & mut Operand2mutable borrowOperand 1 can read and alter data of another operand2

Example

The following example shows a shared borrow and mutable borrow:

fn main() {
    let x = 10;
    let mut y = 13;
    //immutable reference to a variable
    let a = &x;
    println!("Value of a:{}", a); 
    println!("Value of x:{}", x); // x value remains the same since it is immutably borrowed
    //mutable reference to a variable
    let b = &mut y;
    println!("Value of b:{}", b);
    println!("Value of y:{}", y); // y value is changed since it is mutably borrowed
}

output:-

Value of a:10
Value of x:10
Value of b:13
Value of y:13
Dereferencing Operator

Once you have a mutable reference to a variable, dereferencing is the term used to refer to changing the value of the referenced variable using its address stored in the referring variable.

The following illustration shows that operand 1 mutably borrows the value of operand 2 using & mut and then operand 1 dereferences the value of operand 2 using the * operator:

Type

The following table shows the dereferencing operator * along with its function .

operatoroperationexplanation
*Operand1 = Operand2Dereferencing a valuepoint to the value of a mutable borrow variable and can also

update that variable value |

Example

The following example shows how to dereference a variable:


fn main() {
    //mutable reference to a variable
    let mut x = 10;
    println!("Value of x:{}", x);
    let a = & mut x;
    println!("Value of a:{}", a);
    //dereference a variable
    *a = 11;
    println!("Value of a:{}", a);
    println!("Value of x:{}", x); // Note that value of x is updated
}

output:

Value of x:10
Value of a:10
Value of a:11
Value of x:11

Precedence and Associativity

Precedence

The precedence of an operator determines which operation is performed first in an expression with more than one operators.

Operators are listed below in the order of their precedence from highest to lowest :

  • Unary

    • Logical/Bitwise NOT - !

    • Dereference - *

    • Borrow - &, &mut

  • Binary

    • Typecast - as

    • Multiplication- *, Division - /, Remainder- %

    • Addition - +, Subtraction - -

    • Left Shift - <<, Right Shift - >>

    • Bitwise AND - &

    • Bitwise XOR - ^

    • Bitwise OR - |

    • Comparison - == != <`` > <= ``>=

    • Logical AND - &&

    • Logical OR - ||

    • Range - start .. stop

    • Assignment/Compound Assignment - = += -= *= /= %= &= |= ^= <<= >>=

Note: The operators that are written in the same row have the same order of precedence.
Associativity

If two or more operators of the same precedence appear in a statement, then which operator will be evaluated first is defined by the associativity.

Left to Right Associativity

Left associativity occurs when an expression is evaluated from left to right. An expression such as a ~ b ~ c, in this case, would be interpreted as (a ~ b) ~ c where ~ can be any operator. The operators below can be chained as left associative.

📝The comparison, assignment, and the range operator cannot be chained at all.

Example 1

The example below solves an expression according to its operator precedence:

fn main() {
    println!("Answer : {}",( 3 + 5 ) * 9 / 7 & 8);
}

output:-

Answer : 8

Example 2

The example below solves an expression according to its operator precedence:

fn test() {
    println!("{}", 2 + 3 / 5 ^ 7 & 8 | 9);
}

output:

11

If Expression

There can be multiple conditional constructs using an if statement.

  • If expression

  • If…else expression

  • If…else if…else expression

  • Nested if expression

  • Shorthand if expression

Let’s discuss each one of them in detail:-

If Expression

If expression takes a condition. If the condition within the if expression evaluates to be true, then the block of code is executed.

  • Syntax The general syntax is:

    llustration

    The following flow chart explains the concept of an if statement:

    fn main() {
          //define a variable  
          let learn_language = "Rust";
          // if construct 
          if learn_language == "Rust" { 
             println!("You are learning Rust language!");
          }
    }
    

    output

    Your are learning Rust langauage!
    

    If…else Expression

    In an if..else construct, if the condition within the if expression evaluates to be false, then the statement within the else block is executed.

    • Syntax
The general syntax is:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671412484707/sFpgHU3Z4.png align="center")

#### llustration

The following flow chart explains the concept of an if..else if..else expression:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1671412302685/Nzg-CBSL-.png align="center")
fn main() {
    //define a variable 
    let learn_language = "Rust";
    // if else construct 
    if learn_language == "Rust" { 
        println!("You are learning Rust language!");
    }
    else {
      println!("You are learning some other language!");
    } 
}

Output

You are learning Rust language!

if…else if…else Expression

If there are multiple conditions to be checked, then if..else if..else construct is used.

Syntax The general syntax is:

llustration

The following flow chart explains the concept of an if..else if..else expression:

fn main() {
      //define a variable 
      let learn_language="Rust";
      // if..elseif..else construct 
      if learn_language == "Rust" { 
         println!("You are learning Rust language!");
      }
      else if learn_language == "Java" { 
         println!("You are learning Java language!");
      }
      else {
         println!("You are learning some other language!");
      } 
}

Output

You are learning Rust language!
Nested if Expression

An if expression inside the body of another if expression is referred to as a nested if expression.

  • Syntax An if construct is enclosed within an if construct. The general syntax is:

Note: The nested if expression can also be written with a AND expression in an if.

if condition1 && condition2 
{
 //statement
}

This is true only if the second if statement is the only thing inside the first if.

illustration

The following flow chart explains the concept of a nested if statement .

Note: There can be as many levels of nesting as you want.

fn main() {
    //define a variable 
    let learn_language1 = "Rust";
    let learn_language2 = "Java";
    // outer if statement
    if learn_language1 == "Rust" {  // inner if statement
        if learn_language2 == "Java"{
              println!("You are learning Rust and Java language!");
        }
    }
    else {
      println!("You are learning some other language!");
    } 
}

output:-

You are learning Rust and Java languages

Shorthand if

Instead of writing a lengthy if-else construct, we can use a shorthand if.

Syntax The general syntax is:

Note: This is similar to a ternary operator in languages like C and C++.

fn main() {
   //define a variable  
   let learn_language = "Rust";
   // short hand construct
   let res= if learn_language == "Rust" {"You are learning Rust language!"} else {"You are learning some other language!"};
   println!("{}", res);
}

Note: Expressions can return a value, unlike statements. Recall that the semicolon turns any expression into a statement. It throws away its value and returns a unit () instead.

fn main() {
    let x = "Rust";

    let y: bool = if x == "Rust" { true } else { false };

    // let z: bool = if x == "Rust" { true; } else { false; };

    println!("x:{}", x);
    println!("y:{}", y);

Note: Uncommenting line 6 in the above code gives an error ❌ since we are trying to convert an expression to a statement and hence not returning a value.

If Let Expression
What Is an if let Expression?

if let is a conditional expression that allows pattern matching. The block of code in the construct executes if the pattern in the condition matches with that of scrutinee expression.

  • Syntax The if let expression begins with an if followed by a let and then a pattern having values enclosed within round brackets. Then an equal to (=) followed by a scrutinee expression. Then there is a block of code enclosed within braces{}.Then there is also an optional else block after this.

Note: When it says matching of pattern, it means that the defined pattern has the same number of values as that of the scrutinee expression.

Examples

The following examples show how the different cases of if let expression can work:

fn main() {
    // define a scrutinee expression    
    let course = ("Rustlabs", "beginner","course");
    // pattern matches with the scrutinee expression
    if let ("Rustlabs", "beginner","course") = course {
        println!("Wrote all values in pattern to be matched with the scrutinee expression");
    } else {
        // do not execute this block
        println!("Value unmatched");
    }
}

output:-

Wrote all values in pattern to be matched with the scrutinee expression

If the first value or second value matches, it can guess the third value.


fn main() {
    // define a scrutinee expression    
    let course = ("Rustlab", "beginner","course");
    // pattern matches with the scrutinee expression
    if let ("Rustlab", "beginner", c) = course {
        println!("Wrote first two values in pattern to be matched with the scrutinee expression : {}", c);
    } 
    else {
        // do not execute this block
        println!("Value unmatched");
    }
}

output :

Wrote first two values in pattern to be matched with the scrutinee expression : course

If the first value matches, it can guess the other two values.

fn main() {
   // define a scrutinee expression     
   let course = ("Rustlabs", "beginner","course");
   // pattern matches with the scrutinee expression
   if let ("Rustlabs", c, d) = course {
       println!("Wrote one value in pattern to be matched with the scrutinee expression.Guessed values: {}, {}",c,d);
   } else {
       // do not execute this block
       println!("Value unmatched");
   }
}

output:-

Wrote one value in pattern to be matched with the scrutinee expression.Guessed values: beginner, course
Case 2: When the Pattern is Not Matched

In the example below, the defined pattern does not match with the scrutinee expression so the statement in the else block gets executed.

fn main() {
    // define a scrutinee expression     
    let course = ("Rust", "beginner");
    // pattern does not match with the scrutinee expression
    if let ("Java", c) = course {
        println!("Course is {}", c);
    } else {
        // execute this block
        println!("Value unmatched");
    }
}

output:-

Value unmatched

Case 3: When the Pattern is Replaced With _

In the example below, the pattern is not defined. Rather it is replaced with an _. In this case, the statement within the if let block executes.

Note: A warning, ⚠️, is generated by the compiler because the Rust compiler complains that it doesn’t make sense to use if let with an irrefutable pattern.


fn main() {
    // no pattern is define
    if let _ = 10 {
        println!("irrefutable if-let pattern is always executed");
    }
}

output:-

irrefutable if-let pattern is always executed
warning: irrefutable if-let pattern
 --> main.rs:3:5
  |
3 | /     if let _ = 10 {
4 | |         println!("irrefutable if-let pattern is always executed");
5 | |     }
  | |_____^
  |
  = note: `#[warn(irrefutable_let_patterns)]` on by default

Match Expression

What Is a match Expression?

Match expression checks if the current value corresponds to any value within the list of values. Match expressions are similar to switch statements in languages like C and C++. They give a more compact code when compared with the if/else construct.

  • Syntax Match expression uses a matching keyword. The match expression can be written in two different ways, which are given below:

  • Method 1: If you do not want to assign a value to the result variable from within the match block

    fn main() {
        // define a variable 
        let x = 5;
        // define match expression
        match x {
            1 => println!("Java"),
            2 => println!("Python"),
            3 => println!("C++"),
            4 => println!("C#"),
            5 => println!("Rust"),
            6 => println!("Kotlin"),
            _ => println!("Some other value"),
        };
    }
    

    output:-

    Rust
    

    Method 2:

    If you want to assign a value to the result variable from within the match block

fn main(){
   // define a variable
   let course = "Rust";
   // return value of match expression in a variable
   let found_course = match course {
      "Rust" => "Rust",
      "Java" => "Java",
      "C++" => "C Plus Plus",
      "C#" => "C Sharp",
      _ => "Unknown Language"
   };
   println!("Course name : {}",found_course);
}

output:-

Course name : Rust
Comparison of The Different Conditional Constructs
Use if Statement
  • Use an if statement if:

    • It is desired to test the truthiness of an expression

    • There is a single affirmative test

    • There is a need to evaluate different expressions for each branch

    • It can test expressions based on ranges of values or conditions

Use match Statement

  • Use a match statement if:

    • It is desired to compare multiple possible conditions of an expression

    • The values belong to the same domain

    • It can test expressions based on values only, i.e., condition cannot take ranges

Use if let Statement

  • Use an if let statement if:

    • There is a pattern in the condition and it is to be matched with the scrutinee expression

What Is a Loop?

Loops are used to iterate until a defined condition is reached.

Types of Loops

There are two types of loops in Rust:

  • Definite Loops :- Loops in which the number of iterations is known at compile time.

  • Example

    • for
  • Indefinite Loops :- Loops in which the number of iterations is not known at compile time.

  • Example

    • while

    • loop

Definite Loop - For Loop

What Is a for Loop?

A for loop is a definite loop, meaning, the number of iterations is defined.

Syntax

The for loop is followed by a variable that iterates over a list of values. The general syntax is :

Example

The following example uses a for loop that prints 5 numbers.

fn main() {
    //define a for loop 
    for i in 0..5 {
      println!("{}", i);
    }
}

output

0
1
2
3
4
  • An explanation

    for loop definition

    • On line 3 a for loop is defined.

      • Variable i is an iterator variable that iterates over the range with the lower bound as 0 and the upper bound as 5. From here the body of the loop starts.
  • for loop body

    • The body of the for loop is defined from line 3 to line 5

    • In each iteration:

      • On line 4, the value of the variable i is printed.

      • The value of the variable i is incremented by 1.

  • The iterator variable i traverses over the range until the upper bound is reached.

Note: The lower bound is inclusive and the upper bound is exclusive in the range

The following illustration explains this concept:

Enumerate

To count how many times the loop has already executed, use the .enumerate() function.

  • Syntax The general syntax is :

  • Example

The example below prints the frequency of iterations and the value of variable.

fn main() {
  for (count, variable) in (7..10).enumerate() {
      println!("count = {}, variable = {}", count, variable);
  }
}

output

count = 0, variable = 7
count = 1, variable = 8
count = 2, variable = 9

Explanation

  • enumerated for loop definition

    • On line 2 an enumerate for loop is defined.

    • The variable variable iterates over the range with the lower bound as 7 and the upper bound as 10 and a variable count which shows how many times the loop is iterated. From here the body of the loop starts.

  • enumerated for loop body

    • On line 3, the value of count and variable is printed and then incremented by 1.

indefinite Loop - While and Loop

  • While loop

While loop also iterates until a specific condition is reached. However, in the case of a while loop, the number of iterations is not known in advance.

  • Syntax The while keyword is followed by a condition that must be true for the loop to continue iterating and then begins the body of the loop. the general syntax is :

  • Example

The following example makes use of a while loop to print a variable value. The loop terminates when the variable’s value modulus 3 equals 1


fn main() {
  let mut var = 1; //define an integer variable
  let mut found = false; // define a boolean variable
  // define a while loop
  while !found {
      var=var+1;
      //print the variable
      println!("{}", var);
      // if the modulus of variable is 1 then found is equal to true
      if var % 3 == 1 {
        found = true; 
      }
      println!("Loop runs");
  }
}

output

2
Loop runs
3
Loop runs
4
Loop runs

Explanation

  • A mutable variable, var, is defined on line 2.

  • A mutable variable, found, is defined on line 3.

while loop definition

while loop is defined on line 5. while loop is followed by a variable found. found is initially set to false. !found means that the loop will continue to iterate until the value of found evaluates to be true.The loop terminates when found is set to true. From here the body of the while loop starts

while loop body

  • The body of the loop is defined from line 5 to line 14.

  • In each iteration:

    • The value of the variable var is incremented by 1 on line 6 and then printed on line 8.

    • If the value of the var modulus 3 is equal to 1, then the value of found is set to true else it prints “loop runs” on line 13 and the loop continues.

The following illustration traces the execution of the program:

Loop

If you want the iteration to continue infinitely, then use the loop keyword before the block of code.

  • Syntax

The loop keyword is followed by the body of the loop enclosed within curly brackets {}. The general syntax is :

Example

The following example shows how the loop runs infinitely using a loop. Note: The maximum time that is set for the code to run on our platform is 30sec. Since the code below runs more than that, it won’t execute here. However, the code will continue to run indefinitely.

fn main() {
  //define an integer variable
  let mut var = 1; 
  // define a while loop
  loop {
      var = var + 1;
      println!("{}", var);
  }
}

output:

Loop continues infinite times
  • Explanation

  • A mutable variable var is defined on line 3.

  • loop definition

    • loop is defined on line 5.

From here the body of the loop starts

  • loop body

    • The body of the loop is defined from line 5 to line 8.

    • In each iteration, the value of the variable var is incremented by 1 on line 6 and then printed on line 7.

    • The loop continues to iterate infinitely.

Break Statement

What Is a break Statement?

The break statement terminates the loop. It is generally placed inside a conditional statement so that the loop terminates if the associated condition is true.

Break statement is valid in case of while, for and loop.

Using With a for Loop

Below is an example of break expression, using a for loop.

  • The range defined in the for loop is from 0 to 10.

  • Within the for loop :

    • The value of i is printed

    • When the value of i is equal to 5, the loop terminates

fn main() {
  // define a for loop
  for i in 0..10 {
    println!("i:{}", i);
    if i == 5 {
      break;
    }
  }
}

output

i:0
i:1
i:2
i:3
i:4
i:5

Using With a while Loop

Below is an example of break expression, using a while loop.

  • A mutable variable i is defined

  • A boolean variable found is defined Within the while loop body :

    • The value of i is printed

    • When the value of i is equal to 5, the loop terminates

fn main() {
  let mut i = 1;
  let found = false;
  // define a while loop
  while !found {
    println!("i:{}", i);
    if i == 5 {
      break;
    }
    i = i + 1;    
  }
}

output

i:1
i:2
i:3
i:4
i:5

Using With a loop

Below is an example of break expression, using a loop.

  • A mutable variable i is defined

  • Within the loop body:

    • The value of i is printed

    • When the value of i is equal to 4, the loop terminates

The infinite loop is turned into a “manageable” loop.

fn main() {
 let mut i = 1;
 // define a loop
 loop{
   println!("i:{}", i);
   if i == 5 {
     break;
   }
   i = i + 1;    
 }
}

Output

i:1
i:2
i:3
i:4
i:5

Continue Statement

  • What Is a continue Statement?

The continue statement, when encountered inside a loop, skips the execution of the rest of the statements in the loop’s body for the current iteration and returns the control to the start of the loop.

Using With a for Loop

  • Below is an example of a continue expression, using a for loop.

  • The range defined in the for loop is from 0 to 10 with var variable used for iterating over the loop

    • Within the for loop:

      • The value of var is printed

      • When the value of var is equal to 4, the control goes to the start of the loop

      • The loop executes until the upper bound for the defined range is reached

  fn main() {
  // define a for loop
  for var in 0..10 {
     if var == 4 {
        println!("I encoutered a continue statement");
        continue;
      }
      println!("var: {}", var);
      println!("I did not encounter continue statement");
  }
}

output:-

var: 0
I did not encounter continue statement
var: 1
I did not encounter continue statement
var: 2
I did not encounter continue statement
var: 3
I did not encounter continue statement
I encoutered a continue statement
var: 5
I did not encounter continue statement
var: 6
I did not encounter continue statement
var: 7
I did not encounter continue statement
var: 8
I did not encounter continue statement
var: 9
I did not encounter continue statement

Using With a while Loop

  • Below is an example of continue expression, using a while loop.

    • A mutable variable var is defined

    • A boolean variable found is defined

  • Within the while loop body:

    • The value of var is printed

    • When the value of var is equal to 4, the control goes to the start of the loop.

    • The loop executes until the value of found does not equal true.

fn main() {
    // define an integer variable
    let mut var = 1; 
    // define a boolean variable
    let mut found = false;
    // define a while loop
    while !found {
      var = var + 1;
      println!("{}", var);

      if var == 4 {
          println!("I encoutered a continue statement");
          continue;
        }
        println!("I did not encounter continue statement");

        if var == 10{
          found = true;
        }
    }
}

Output

2
I did not encounter continue statement
3
I did not encounter continue statement
4
I encoutered a continue statement
5
I did not encounter continue statement
6
I did not encounter continue statement
7
I did not encounter continue statement
8
I did not encounter continue statement
9
I did not encounter continue statement
10
I did not encounter continue statement

Using With a loop

  • Below is an example of continue expression, using a loop .

    • A mutable variable var is defined

    • A boolean variable found is defined

  • Within the loop body:

    • The value of var is printed

    • When the value of var is equal to 4 , the control goes to the start of the loop

    • The loop executes infinitely

Note: This code widget will give an error, ❌, due to limitations of our platform but on the local machine, it will run an infinite loop.

fn main() {
  // define an integer variable
  let mut var = 1; 
  // define a loop
  loop {
    var = var + 1;
    println!("{}", var);

     if var == 4 {
        println!("I encoutered continue statement");
        continue;
      }
      println!("I did not encounter continue statement");
  }
}

Nested Loops

  • What Is a Nested Loop? A nested loop is a loop within a loop.

  • Syntax Here, a for loop nested inside a for loop. However, any loop can be nested inside any loop. The general syntax is :

Example

The following code prints a multiplication table of 1-5 using a nested for loop.

fn main() {
 for i in 1..5{ //outer loop
  println!("Multiplication Table of : {}", i);
   for j in 1..5 { // inner loop
       println!("{} * {} = {}", i, j, i * j);
   }
 }
}

output

Multiplication Table of : 1
1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
1 * 4 = 4
Multiplication Table of : 2
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
Multiplication Table of : 3
3 * 1 = 3
3 * 2 = 6
3 * 3 = 9
3 * 4 = 12
Multiplication Table of : 4
4 * 1 = 4
4 * 2 = 8
4 * 3 = 12
4 * 4 = 16

Explanation

  • Outer for Loop

    • A for loop is defined on line 2

    • The loop takes i as an iterator that iterates over values from 1 to 5.

  • Inner for Loop

    • A for loop is defined on line 4

    • The loop takes j as an iterator that iterates over values from 1 to 5.

    • For each iteration of the outer loop, the inner loop runs four times while printing the product of variables i and j.

ijoutput
11
2
3
41 *1 = 1
1* 2 = 2
1 *3 = 3
1* 4 = 4
21
2
3
42 *1 = 2
2* 2 = 4
2 *3 = 6
2* 4 = 8
31
2
3
43 *1 = 3
3* 2 = 6
3 *3 = 9
3* 4 = 12
41
2
3
44 *1 = 4
4* 2 = 8
4 *3 = 12
4* 4 = 16

Sometimes, you might need to break the outer loop instead of the inner loop. So how can you specify which loop you are referring to? You can use a loop label.

Loop Labels

  • What Is a Loop Label? A loop label assigns an identifier to a loop.

  • Syntax Write a label and colon before the loop.

  • Example The code below prints the multiplication table of 1 to 5 except 3. See how specifying a loop label helps you to skip the table of 3.
fn main() {
 'outer:for i in 1..5 { //outer loop
    println!("Muliplication Table : {}", i);
   'inner:for j in 1..5 { // inner loop
        if i == 3 { continue 'outer; } // Continues the loop over `i`.
        if j == 2 { continue 'inner; } // Continues the loop over `j`.
        println!("{} * {} = {}", i, j, i * j);
   }
 }
}

output:

Muliplication Table : 1
1 * 1 = 1
1 * 3 = 3
1 * 4 = 4
Muliplication Table : 2
2 * 1 = 2
2 * 3 = 6
2 * 4 = 8
Muliplication Table : 3
Muliplication Table : 4
4 * 1 = 4
4 * 3 = 12
4 * 4 = 16

Explanation

  • Outer for Loop

    • A for loop is defined on line 2.

    • The loop has a label outer . It takes i as an iterator that iterates over values from 1 to 4.

  • Inner for Loop

    • A for loop is defined on line 3

    • The loop has a label inner. It takes j as an iterator that iterates over values from 1 to 5.

    • For each i the inner loop iterate j times and prints the product i * j.

    • When the outer loop increments i to 3 and the inner loop starts from j = 1, the condition i == 3 is found to be true and the continue ‘outer statement causes execution to be transferred to the next iteration of the outer loop on line 2. The variable i is incremented to 4 and the execution continues.

    • When the value of j increments to 2, then the 2nd iteration of the inner loop gets skipped and continue 'inner causes the execution to be transferred to the next iteration of the inner loop on line 4. The variable j is incremented to 3 and the execution continues.

this series will continue with more hand-on labs .

Did you find this article valuable?

Support CloudNativeFolks Community by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this