Learning-Rust

Rust is to be installed using official cargo distribution.

Rustlings course

TIP

Installed with cargo install rustlings and initialised with rustlings init

Intro

Lines are printed with println!() and the main function is defined as fn main().

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

Variables

Variables are declared with let keyword and the type is defined with :. Mutable variables need to be defined with mut keyword. Same variable can be reused by changing its type.

fn main(){
    let num = "THREE";
    println!("The number is {num}");
    let mut num:i32 = 10;
    println!("The number is {num}");
    num = 100;
    println!("The number is {num}");
}

Functions

fn call_me(num: i32) -> i32 {
    return num*num;
}
fn main(){
    let square  = call_me(32);
    println!("The square of 32 is {square}");
}

If-Else

fn check_str(fruit: String) -> i32 {
    if fruit == "apple" {
        1
    } else if fruit == "orange" {
        2
    } else {
        3
    }
}

Vectors

Vectors are dynamic arrays. Similar to vector in C++, the vector is stored in the heap with a pointer in the stack. However, since vectors don’t have the Copy trait, they are moved in cases where C++ or Python would shallow copy.

fn main() {
    let mut v: Vec<i32> = vec![10, 20, 30, 40];
    for i in 0..=10 {
        v.push(i);
    }
    let mut v1 = Vec::<i32>::new();
    v1.push(5);
    let mut v2 = v;
    v2.push(11);
}

Mapping over Vectors

Vectors can be mapped using the iter() method and map() function.

fn double_element(vector: Vec<i32>) -> Vec<i32> {
    vector
        .iter()
        .map(|v| v * 2)
        .collect()
}

Move semantics

Rust uses the system of borrowing and moving to manage memory. In this system, unless specified, the ownership of a variable is passed to any operation. In cases where the operation has its own scope, such as passing a variable to a function, the variable is moved to the function scope so the function will own the variable. To avoid this a reference to a variable a can be passed with &a and a reference b can be dereferenced with *b.

fn main() {
    let s:String = String::from("Hello");
    take_ownership(s);
    let mut s1: String = give_ownership("Hello");
    println!("Main received {}", s1);
    let s2: String = take_and_give_ownership(&mut s1);
    println!("Main received {}", s2);
}
 
fn take_ownership(s: String) {
    println!("Took ownership of {}",s);
}
 
fn give_ownership(s: &str) -> String {
    s.to_string()
}
 
fn take_and_give_ownership(s:&mut String) -> String {
    s.push_str(", World!");
    s.to_string()
}

Multiple references

Every reference of a variable is kept in memory until the last time it is used, after which it is cleared. This memory management is decided at compile time. If a mutable reference to a variable exists, no other reference to the variable can exist. This is to prevent multiple sources of write to the variable which can cause data races. However, any number of immutable references can exist simultaneously as they cant overwrite the variable.

fn main() {
    let mut num: i32 = 5;
    let num1 = &num;
    let num2 = &num; // Valid as existing ref is immutable
    println!("{}, {}", num1, num2); // num1 and num2 cleared as they are never used again
    let num3 = &mut num;
    // let num4 = &num; // Using this would be invalid as existing ref num3 is mutable
    println!("{}", num3); // num3  cleared as it is never used again
    let num5 = &mut num; // Valid as num3 is never used again
    *num5 += 1; // Writes to num5 which changes num
}