Tag: rust

Loops and conditional statement in Rust

Conditional Statement

An if expression is used to control which code gets executed based on conditions the program can evaluate at runtime.

Short hand to use if condition.

let y =true;
let x = if y {1} else {0};
println!("x is {}", x)

Output-

x is 1

Loop

Rust provides a loop keyword to indicate an infinite loop. Unless stopped manually or with a break staement, loop can go infinitely.

Infinite loop example. Manually stop the program

let mut count=0;

loop{
   count += 1;
   println!("count is {}", count)
}

break keyword

Immediately exit the loop and continue execution forward. The break statement can be used to prematurely exit all types of loop constructs.

Here once loop is at 10 it breaks the loop and continues further execution

let mut count=0;

loop{
   if count == 10{
      println!("breaking the loop on count  {}", count);
      break;
    }
    count += 1;
    println!("count is {}", count);
}
println!("Continues execution.");

Output-

count is 1
count is 2
count is 3
count is 4
count is 5
count is 6
count is 7
count is 8
count is 9
count is 10
breaking the loop on count  10
Continues execution.

break with return

In the below code break can return the result or the reason the loop is stopped to the execution program.

let mut count=0;

let result = loop{
    if count == 10{
        println!("breaking the loop on count  {}", count);
        break count * 5
     }
     count += 1;
     println!("count is {}", count);
};

println!("Count result is - {} * 5 = {}", count, result);

Output-

count is 1
count is 2
count is 3
count is 4
count is 5
count is 6
count is 7
count is 8
count is 9
count is 10
breaking the loop on count  10
Count result is - 10 * 5 = 50

Predicate (while) loop

Predicate loop that repeats a block of code while the condition is true. Begins by evaluating a boolean condition and breaks/completes when the condition is false. A while-loop repeats a block of code while a condition is true.

Here break statement can be used to terminate the loop but it doesnot return the value as comapred to loop

let mut count=0;
while count < 5 {
   count += 1;
   println!("count is {}", count);
}

Output

count is 1
count is 2
count is 3
count is 4
count is 5

Iterator for loop

The for in construct can be used to iterate through an Iterator. One of the easiest ways to create an iterator is to use the range notation a..b. This yields values from a (inclusive) to b (exclusive) in steps of one.

For loops are mainly used to iterate over the items in a collection, and execute some code for each one so that you can process each item in the collection individually. If you need to repeat a block of code for a specific number of times that you know before starting the loop, you can use a ‘for’ loop to iterate over the range from zero to that value.

for n in 1..5{
   print!("{}\t", n)
}

loop 1..5, here 1 is inclusive while 5 is exclusive. Print! won;t print in new line.

Output

1       2       3       4

user iter() methodto iterate over the list-

let message= ['r','u','s','t'];

for msg in message.iter(){
    print!("{}\t", msg)
}

Output-

r       u       s       t

Iterator with index

At times apart from the element in the array or list and index is required. This can be achieved by using enumerate() method

let message= ['r','u','s','t'];

for (index,msg) in message.iter().enumerate(){
     println!("item {} is {}", index, msg)
}

Output-

item 0 is r
item 1 is u
item 2 is s
item 3 is t

Nested loops

Example here taking row and columns and printing those values in a nested loop i.e. for loop(rows) wthin for loop(columns)

let matrix = [[1,2,3],[10,20,30], [100,200,300]];

for row in matrix.iter(){
    for col in row.iter(){
        print!("{}\t", col);
     }
     println!();
}

Output-

All the columsn are printed in same line while row in new line.

variable.iter() is used to iterate between rows and columns

1       2       3
10      20      30
100     200     300

Modify the value in for loop using iter_mut()

Sometimes value in the collection needs to be updated. This can be done by iterating and updating the values. To do so use iter_mut() function as follows-

let mut matrix = [[1,2,3],[10,20,30], [100,200,300]];

for row in matrix.iter_mut(){
   for col in row.iter_mut(){
         *col += 5;
         print!("{}\t", col);
    }
    println!();
 }

Reference – https://dhghomon.github.io/easy_rust/Chapter_17.html

Notice here the iter_mut() method is used which give a reference to the mmeory allocated for the collection. In the next line *col is used to deference so that a addition operation can be performed.

Using * is called “dereferencing”.

Output-

6       7       8
15      25      35
105     205     305

Rust has two rules for mutable and immutable references. They are very important, but also easy to remember because they make sense.

  • Rule 1: If you have only immutable references, you can have as many as you want. 1 is fine, 3 is fine, 1000 is fine. No problem.
  • Rule 2: If you have a mutable reference, you can only have one. Also, you can’t have an immutable reference and a mutable reference together.

Loading

Functions in Rust

Functions are use for efficient programming and eliminates the need to constantly rewrite the code.

Rust is a strictly typed language and therefore needs to know the data type for all variables at compilation.

When there is not a specified return value, the function will return the unit data type which is represented as () and indicates there is no other meaningful values that could be returned. A statement does not return a value.

In the below example we can see how a function can be defined and called from the main.

Function without and with parameters. Define the parmeter data type and return data type.

fn main(){
    display_message();
    display_message_with_parameters(10);
    let addition  = add_numbers(10, 20);
    println!("Addition of 2 number is {}", addition);
}

fn display_message(){
    println!("Display message....");
}

fn display_message_with_parameters(value : i8){
    println!("Function paramter value is {}", value);
}

fn add_numbers(param1: i8, param2: i8) -> i8{
    param1 + param2
}

Output-

Display message....
Function paramter value is 10
Addition of 2 number is 30

Diverging Functions

Diverging functions never return. They are marked using !, which is an empty type.

fn foo() -> ! {
    panic!("This call never returns.");
}

Empty return functions

Function return empty (). It doesn’t return anything but returns back to caller

fn some_fn() {
    ()
}

fn main() {
    let _a: () = some_fn();
    println!("This function returns and you can see this line.");
}

Arrays and tuples in Rust

In Rust arrays are stored in contigious section of memory locations and sorted in order. They are fixed length and cannot be dynamically resize, containing elements of same data type.

A fix size array denotes [T;N]. where T denotes element type i.e. i32, u64 etc. and N denoted non-negative compile time constant size. This means compiler needs to know before running the code the size of array.

One way of declaring an array is to have the values comma seperated in square brackets e.g.- [1, 2, 3]

Example-

let fruits = ["apple", "orange"];
let selected_fruit = fruits[0];

// Retrieve first element of an array.
println!("Selected fruit is - {}", selected_fruit);

Output-

Selected fruit is - apple

To modify the value in the array make the array mutable and change the value of an element in array, in this case first element.

let mut fruits = ["apple", "orange"];
fruits[0]= "mango";

let selected_fruit = fruits[0];
println!("Selected fruit is - {}", selected_fruit);

Output-

Selected fruit is - mango

Repeat expression – Declaring arrays with the data type and length [expr, N] where N is the times to repeat expression (expr).

Note that [expr; 0] is allowed, and produces an empty array. This will still evaluate expr, however, and immediately drop the resulting value, so be mindful of side effects.

let numbers : [i32; 5]; // declare array
numbers= [1, 3, 5, 7, 9]; // initalize array
println!("Selected number - {}", numbers[0]); // print first element

Output-

Selected number - 1

Compiler Errors-

If array is not initalised and the element of array is tried to access the compiler thorws an error-

let numbers : [i32; 5];
println!("Selected number - {}", numbers[0]);

Solution-

let numbers : [i32; 5];
numbers = [0;5]; // All elements has value 0
println!("Selected number - {}", numbers[0]);

Output-

Selected number - 0

Compiler Errors-

Index out of bounds – if the element accessed is larger than the array length.

let numbers : [i32; 5];
println!("Selected number - {}", numbers[5]);

The array length is 5 and index starts from 0, so the last index is 4. But in above program the index 5 is trying to access hence error

Solution-

The element accessed here is the last element i.e. index 4.

let numbers : [i32; 5];
numbers = [0;5]; // all elements has value 0
println!("Selected number - {}", numbers[4]);/ Compiles successfully

Count of elements

Get the length of an array using extension function len()

let numbers : [i32; 5];
numbers = [0;5];
println!("Length of numbers is {}", numbers.len());

Output-

Length of numbers is 5

Slice Array

Slice the array by giving an expression the starting and ending index with double dots between. e.g.- [starting_index..ending_index]. Access the memory of the variable and provide the range e.g.- &array_variable[2..4]

Print the array and the slice using {:?}

Omit start index and end index [..4] and [2..] respectively

let numbers : [i32; 5] = [10, 20, 30, 40, 50];
let array_slice = &numbers[2..4];
let omit_start_index = &numbers[..4];
let omit_end_index = &numbers[2..];
println!("array = {:?}", numbers);
println!("slice = {:?}", array_slice);
println!("omit start index = {:?}", omit_start_index );
println!("omit end index = {:?}", omit_end_index );

Output

array = [10, 20, 30, 40, 50]
slice = [30, 40] // [2..4]
omit start index = [10, 20, 30, 40] // [..4]
omit end index = [30, 40, 50] // [2..]

Multi-dimensional array

Can be declared as follows-

let fruits = [["1", "2"],["apple", "orange"]];
println!("Selected fruit is - {}", fruits[1][1]);

Tuples

Tuples are similar to arrays except that tuples can have mixed data types. Similar to arrays tuples are stored in a fiexed-length and the data types must be know at compile time with elements are ordered.

Tuples is declared with comma seperated elements with round braces – (“hello”, 5 , ‘c’)

Here the first element is string then integer and character. See below example how to declare tuples and access elements.

Example-

// declare and inititaize tuple
let tuple = ("Hello", 5 , 's');
// Access elements
println!("First element in tupple is - {}",tuple.0)

Output-

First element in tupple is - Hello

Explict declaration can be done as follows-

Example-

// declare and inititaize tuple
let tuple: (&str, i32, char) = ("Hello", 5 , 's');
// Access elements
println!("First element in tupple is - {}",tuple.0) 
// Prints "First element in tupple is Hello"

Updating element in tuple

First mark the variable as mutable. Access the element of the tuple and update.

// declare and inititaize tuple
let mut tuple: (&str, i32, char)    = ("Hello", 5 , 's');
// Access second element and add 5
tuple.1 += 5;
// Print updated element
println!("Add 5 to second element in tupple - {}",tuple.1)

While updating the element be careful that the element value does-not exceeds the max value a data type can hold or there will be memory overflow error.

Copy the tuple object

Continuing to the aboce code, decalre the tuple with 3 elements, assign the updated tuple and access the declared variable in tuple.

// declare and inititaize tuple
let mut tuple: (&str, i32, char)    = ("Hello", 5 , 's');
// Access second element and add 5
tuple.1 += 5;
// Print updated element
println!("Add 5 to second element in tupple - {}",tuple.1);

let (first, second , third ) = tuple;
println!("Copied tuple third element is {}", third);

Output-

Add 5 to second element in tupple - 10
Copied tuple third element is s

Warnings-

Above code, first and second element are never used, hence the compiler will give warnings – unused variable:

Although it won’t stop the compiler from running the code. The suggestion here is to use prefix tuple element with _ (underscore) i.e. _first and _second. Lets change this and see if there are no warnings.

    // declare and inititaize tuple
    let mut tuple: (&str, i32, char)    = ("Hello", 5 , 's');
    // Access second element and add 5
    tuple.1 += 5;
    // Print updated element
    println!("Add 5 to second element in tupple - {}",tuple.1);

    let (_first, _second , _third ) = tuple;
    println!("Copied tuple third element is {}", _third);

Now there are no warnings and compiles with results-

Reference

https://doc.rust-lang.org/std/primitive.tuple.html

https://doc.rust-lang.org/rust-by-example/primitives/array.html

Rust Primitive Data types

Rust variables are immutable by default.

Variables must be explicitly declared as mutable using mut keyword

Rust is a statically typed language – all variable data types must be know at compile time.

Example-

//default immutable. value cannot be changed after declaration
let x = 10; 

// marked as mutable. i.e value can be changed after declaration
let mut y = 20; 

Compiler Errors-

let x = 10;
println!("x is {}", x);
x=20; // Error. Cannot be assigned twice. consider making this muttable
println!("x is {}", x);

Solution-

let mut x = 10; // make this mutable
println!("x is {}", x);
x=20; // compiles successfully
println!("x is {}", x);

Reference

https://doc.rust-lang.org/1.0.0/style/style/naming/README.html

https://rust-lang.github.io/api-guidelines/naming.html

Integer Data Types

Reference https://doc.rust-lang.org/reference/types/numeric.html

  • Unsigned – only represent positive values
  • Signed – represent positive and negative values
  • Characterized by number of bits
LengthSignedUnsigned
8-biti8 ( -(27) to 27-1)u8 ( 0 to 28-1)
16-biti16 ( -(215) to 215-1)u16 ( 0 to 216-1)
32-biti32 ( -(231) to 231-1) defaultu32 ( 0 to 232-1)
64-biti64 ( -(263) to 263-1)u64 ( 0 to 264-1)
128-biti128 ( -(2127) to 2127-1)u128 ( 0 to 2128-1)

Type of the varaible is declared after the : (colon)

Example:-

// signed 8-bit integer. max value can hold is 255
let x: i8 = 255;
let y: i8 = -10;

// unsigned 8-bit integer.
let z: u8 = 255;

Compiler Errors-

// unsigned 8-bit integer. max value 255
let z: u8 = 256;

Solution-

// unsigned 8-bit integer. max value 255
let z: u8 = 255; // compiles successfully

Runtime Errors-

// unsigned 8-bit integer. max value 255
let mut z: u8 = 255;
z = z + 1; // Error: panicked - attempt to add with overflow
println!("z is {}", z);

Solution-

// unsigned 8-bit integer. max value 255
let mut z: u8 = 254;
z = z + 1; // compiles and runs successfully
println!("z is {}", z);

Output- z is 255

Floating-Point Data Types

  • Represent numbers with decimal points
  • f32 and f64 are two floating-point types
  • Value stored as fractional and exponential components

f32- represents value from 6 to 9 decimal digits of precision.

f64- represents value from 15 to 17 decimal digits of precision.

Example-

let x= 10.00; // default is f64
println!("x is {} deafult type is f64", x);

let y: f32= 10.1234567890132456798; // default is f64
println!("y is {} with f32 floating-point type", y);

let z: f64= 10.1234567890132456798; // default is f64
println!("z is {} with f64 floating-point type", z);

Output-

x is 10 deafult type is f64
y is 10.123457 with f32 floating-point type
z is 10.123456789013245 with f64 floating-point type

Guidelines for Numeric data types

Values with fractional component or decimal places use floating-point data type.

Use f64 or been default gives most precision range. With modern computers it is as good as using f32. So perrfomance should not be an issue.

Use f32 with embedded systems where memory is a limitation.

Default 32 signed integer (i32) provides a generous range – between and around plus or minus 2 billion

For memory concerns use smaller size integers to conserve and use less memory

Arithmetic Operations

Rust uses arithmetic operations with the operators +, -, *, / and %

Artihmentic operation results depend on the type of variables. example, if both operators are integer then will get result in integer

let x= 10; // default is i32
let y=3; // default is i32
let z= x / y;
println!("z is {}", z) // so z will be i32

Output- Here the value of z is 3 since both the variables are defaulted to signed integer i.e. i32

z is 3

Now if we add decimals to x and y the output is floating-point-

let x= 10.00; // default is f64
let y=3.00;
let z= x / y;
println!("z is {}", z)

Output- Here the default is f64.

z is 3.3333333333333335

Compiler Errors-

If one of the variables data type is different from other, then will receive error-

let x= 10; // default is i32
let y=3.00; // default is f64s
let z= x / y;
println!("z is {}", z)   

Here the default integer is i32 and floating-point is f64. Dividing different data types will result in error. This is applicable for all artihmetic operations.

Solution-

For the above error cast the variable.

 let x= 10; // default is i32
 let y=3.00; // default is f64s
 let z= x as f64 + y;
 println!("z is {}", z)  

Output-

z is 3.3333333333333335

Formatting print statements

Reference- https://doc.rust-lang.org/std/fmt/

To format the data use Module fmt. This uses the formatting extensions.

Use {} as argument to print the values

Examples-

 println!("Hello {}!", "world"); // prints-  Hello world!
 //named parameters
 println!("{value}", value=10); // prints- 10

 // named paarameters using variable
 let name = "Sandeep Pote";
 println!("Name: {name}");

 // Decimal point upto 3
 let z= 3.33333335;
 println!("z is {:.3}", z);  // prints - 3.333

 // Upto 3 decimal places with lenth of 8 and preceding 0's
 println!("z is {:08.3}", z);  // prints- 0003.333

Output-

Hello world!
10
Name: Sandeep Pote
z is 3.333
z is 0003.333

Positional Parameters

Here the first_name and last_name are named parameters, but the position of printing the parameters are set in different order

println!("{last_name} {first_name}", first_name="Sandeep", last_name="Pote"); //Prints- Pote Sandeep

Binary

Binary trait formats the output as a number in binary

Example-

let value= 0b11110101u8;
println!("value is {}", value)// Prints the number
println!("value is {:b}", value) // prints the binary
println!("value is {:010b}", value) // prints the binary upto 10 digits and preceding 0.

Output-

value is 245
value is 11110101
value is 0011110101

Here the binary is converted to number. The prefix 0b mentions the value is binary and suffix u8 converts the binary to unsigned integer.

Bitwise operations-

Logical operations on patterns using NOT, AND, OR, XOR and SHIFT.

Taking the above example, Bitwise NOT-

let mut value= 0b11110101u8;
value=!value;
println!("value is {:08b}", value); // prints the negated binary

Output-

value is 00001010

Bitwise AND- change the value of a specific Bit-

Use the & with the anotehr binary to change the value.

Example- to change the value of the highlighted 11110111 to 0, & this with 11110011. Ouput will be 11110011

Example-

let mut value= 0b11110111u8;
value=value & 0b11110011;
println!("value is {:08b}", value); 

Output-

value is 11110011

Likewise use | (pipe) for OR, ^ for XOR, << Left shift and >> Right Shift

Boolean data types and operations

Use the above operatos used in bitwise for boolean operations.

Example-

let a = true;
let b= false;
println!("a is {} and b is {}", a, b);
println!("NOT a is {} ", !a);
println!("a AND b is {} ", a & b);
println!("a OR b is {} ", a | b);
println!("a XOR b is {} ", a ^ b);

Output-

a is true and b is false
NOT a is false 
a AND b is false 
a OR b is true 
a XOR b is true 

Short-Circuiting Logical Operations

With this in OR operation if the left side is True then it won’t check the right side since the result for OR will be always true.

Similarly, for AND operation if the left side is False, it won’t check the value at the right side, since the result will be always False.

This can be done using && or ||. This improves the code effeciency.

Example-

let a = true;
let b= false;
let c= (a ^ b) || (a & b);
println!("c is {}", c);

Output-

c is true // (a ^ b) is true || (a & b) is false

Comparision Operations

Reference – https://doc.rust-lang.org/book/appendix-02-operators.html

Use compareision operators to compare values. Some of these are-

  • Equal ==
  • Not Equal !=
  • Greater Than >
  • Less Than <
  • Greater Than Equal >=
  • Less Than Equal <=

Example-

let a = 1;
let b= 2;
println!("a is {} and b is {}", a, b);
println!("a EQUAL TO b is {} ", a == b);
println!("a NOT EQUAL TO b is {} ", a != b);
println!("a GREATER THAN TO b is {} ", a > b);
println!("a LESS THAN TO b is {} ", a < b);
println!("a GREATER THAN EQUAL TO b is {} ", a >= b);
println!("a LESS THAN EQUAL TO b is {} ", a <= b);

Output-

a is 1 and b is 2
a EQUAL TO b is false 
a NOT EQUAL TO b is true 
a GREATER THAN TO b is false 
a LESS THAN TO b is true 
a GREATER THAN EQUAL TO b is false 
a LESS THAN EQUAL TO b is true 

The comparision can be used with the similar data types.

Char Data Type

  • Represents a single character
  • Stored using 4 bytes
  • Unicode scalar value

Char can be represented within a single quote e.g.- ‘a’

Unicode characters can be represented using “\u{hexa decimal value}”

Reference- https://home.unicode.org/

Example-

let letter = 'a';
let number = '1';
let unicode= '\u{0024}';
println!("Letter is {}", letter);
println!("Number is {}", number);
println!("Unicode is {}", unicode);

Output-

Letter is a
Number is 1
Unicode is $

Loading

First Rust project using cargo package manager

If this is the first time you are using Rust you will have to install Rust on your Windows machine.

To install see this link – https://www.rust-lang.org/tools/install

To install Rust on Windows subsystem for Linux, use this shell command-

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

Check the version of Rust-

rustup --version

Check the version of Rust compiler

rustc --version

To update the Rust, use following command-

rustup update

Create a main.rs file and add code to print a string

fun main(){
    println!("Hello World!")
}

Compile using rust compiler

rustc main.rs

This should create a pdb and exe file

Execute the main.exe file and this should print the string

Create a Rust project using Cargo

Cargo is a build and package manager.

  • Helps to manage dependecies for repetable builds
  • Downloads and builds external libraries
  • calls rustc with correct parameters
  • Included with standard rustup installation

For creating a new Rust project using Cargo package manager-

cargo new first_rust_prj

Tip – The name of the project should be snake case. Example this will give a warning. Atlhough this will create a project.

cargo new FirstRustPrj //Warning
 
Creating binary (application) `FirstRustPrj` package
warning: the name `FirstRustPrj` is not snake_case or kebab-case which is recommended for package names, consider `firstrustprj`

This should create following folder structure with main.rs and Cargo.toml (configuration) file.

TOML – Tom’s Obvoius Minimal Language.

Alternatively, you can create a project without using Cargo. Create a src directory and create appropriate Cargo.toml namually. To create toml file use init command

cargo init

Building and Running Cargo project

Build the Cargo project using following command-

cargo build

To compile and run directly from the project folder, use following command-

cargo run

Error compiling the project-

error: linker link.exe not found

So the pre-requisite for windows machine is to have MS C++ build tools. Install the smae from here. It should be around 5 GB.

https://visualstudio.microsoft.com/visual-cpp-build-tools

Now this should run the project –

To check the code without producing an executable use follwoing command-

cargo check

Building for Release

Use following command when project is ready for release-

cargo build --release

IDE to develop rust-

Visual Studio Code

Install Visual Studio Code. Then install rust-analyzer for code auto completion, hints and code actions – Link

See here the IDE’s the rust analyser supports.

Rust Rover

IDE by Jet Brains – here. This is free for non-commercial users.

References-

https://doc.rust-lang.org/book/ch01-01-installation.html#installing-rustup-on-linux-or-macos

Loading

All about Rust Cargo commands

Cargo is Rust’s build system and package manager. Most developers use this tool to manage their Rust projects because Cargo handles a lot of tasks for you, such as building your code, downloading the libraries your code depends on, and building those libraries.

Cargo commands and its usage-

Check the cargo version

cargo --version

Create a new project

Create a new project using Cargo.

cargo new <<project_name>>

Build the Cargo project

Build command compiles the project and create’s an executable file in /debug/ folder. Running the cargo build command updated the Cargo.lock file which keeps the trakc of exact version of dependecies in the project.

cargo build

Running a Cargo project

Cargo run compiles the project and runs the resultant executable all in one command. So instead of remembering the cargo build and path to execute the excutable, Cargo run does both for you which is more conveninent

cargo run

Check your code

If you want to quickly check if the code to make sure it complies susccesfully without using build or run command, you can use check command. This nmusch faster than build because it skips the step to create an executable.

cargo check

Update the project dependencies

Update the dependeincies of you project using update command. This ignores the Cargo.lock file which has all the dependencies. update command will look for one higher version than current version e..g: if the current version is 0.7.5 and the latest version is 0.8.0. But if there is next higher version i.e. 0.7.6, then update command will udpate the dependency to 0.7.6. Also after update the Cargo.lock file is udpated.

If you want to jump to a specific version, as per above case to 0.8.0. Update hte Cargo.toml file dependencies section.

cargo update

Reference – https://doc.rust-lang.org/cargo/

This is WIP, there is lot more to add here.

Loading