Take a note when learning Rust.

1. Shadowing

Declare a new variable with the same name as a previous variable. The first variable is shadowed by the second. In other word, the second variable overshadows the first. So, we will get the second variable when using the variable name, util the second variable shadowed by other variables, or the scope ends. Shadowing is different from mutating a variale by using a mutable varible through mut. The variable is still immutable after we using shadowing. Also, shadowing allows variable type change.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() { 
// This is the first time we declare x
let x = 5;

// This is the second time we declare x, value 6 shadows 5
let x = x + 1;

{
// In a new scope, declare x to be 12
let x = x * 2;

println!("The value of x in the inner scope is: {x}");
// End of the scope, out of this scope, 12 shadowing 6 will end.
}

println!("The value of x is: {x}");
}

Output:

1
2
The value of x in the inner scope is: 12
The value of x is: 6

2. Integer Type

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

Signed variant can store numbers from -2^(n - 1) to 2^(n - 1) - 1.
Unsigned variant can store numbers from 0 to 2^n -1.

3. Tuple Type

Tuple is a compound type.

  • Tuples havee a fixed length
  • Create a tuple by writing a comma-separated list of values inside parentheses.
  • Use pattern matching to destructure a tuple value
  • Access a tuple element directly by using a period .
1
2
3
4
5
6
7
8
9
10
11
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("Values of x, y, z are: {x}, {y}, {z}");

let i = tup.0;
let j = tup.1;
let k = tup.2;

println!("Values of i, j, k are: {i}, {j}, {k}");
}

Output:

1
2
Values of x, y, z are: 500, 6.4, 1
Values of i, j, k are: 500, 6.4, 1

4. Array type

1
2
3
4
5
6
7
8
9
fn main() {
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [6, 7, 8, 9, 0];
let c = [3; 5];

println!("Array a is: {:?}", a);
println!("Array b is: {:?}", b);
println!("Array c is: {:?}", c);
}

Output:

1
2
3
Array a is: [1, 2, 3, 4, 5]
Array b is: [6, 7, 8, 9, 0]
Array c is: [3, 3, 3, 3, 3]

5. Interesting expression usage

We can assian an expression to a variable, since expression can evaluates to a value.

1
2
3
4
5
6
7
8
fn main() {
let y = {
let x = 3;
x + 1
};

println!("The value of y is: {y}");
}

Output:

1
The value of y is: 4

6. Loop

Here are a couple of loop methods interesting.

  • Loop can return a value
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    fn main() {
    let mut counter = 0;

    let result = loop {
    counter += 1;

    if counter == 10 {
    break counter * 2;
    }
    };

    println!("The result is {result}");
    }
    Output:
    1
    The result is 20
  • Loop labels to disambiguate between multiple loops
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    fn main() {
    let mut count = 0;
    'counting_up: loop {
    println!("count = {count}");
    let mut remaining = 10;

    loop {
    println!("remaining = {remaining}");
    if remaining == 9 {
    break;
    }
    if count == 2 {
    break 'counting_up;
    }
    remaining -= 1;
    }

    count += 1;
    }
    println!("End count = {count}");
    }
    Output:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    count = 0
    remaining = 10
    remaining = 9
    count = 1
    remaining = 10
    remaining = 9
    count = 2
    remaining = 10
    End count = 2

7. Ownership rules

  • Each value in Rust has an owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

8. Move

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let x = 5;
let y = x; // copy integer value 5 from x, so two value 5 on the stack

println("{}, {}", x, y); // OK here, we could get 5, 5 here

let s1 = String::from("hello");
let s2 = s1; // move previous s1 value "hello" to s2

println!("{}, world!", s1); // Not OK! Throw error, since "hello" moved to s2
}

The reason is that types such as integers that have a known size at compile time are stored entirely on the stack, so copies of the actual values are quick to make.

Rust has a special annotation called the Copy trait that we can place on types that are stored on the stack. Here are some of the types that implement Copy:

  • All the integer types, such as u32.
  • The Boolean type, bool, with values true and false.
  • All the floating point types, such as f64.
  • The character type, char.
  • Tuples, if they only contain types that also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.

8. References and borrowing

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.

Examples

  • Can not borrow as mutable more than once at a time
    1
    2
    3
    4
    5
    6
    7
    8
    fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s; // BIG PROBLEM

    println!("{}, {}", r1, r2);
    }
  • Can not have a mutable reference while we have an immutable one to the same value
    1
    2
    3
    4
    5
    6
    7
    8
    9
    fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

    println!("{}, {}, and {}", r1, r2, r3);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{} and {}", r1, r2);
    // variables r1 and r2 will not be used after this point

    let r3 = &mut s; // no problem
    println!("{}", r3);
    }

9. Dangling references

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let reference_to_nothing = dangle();
}

fn dangle() -> &String { // dangle returns a reference to a String

let s = String::from("hello"); // s is a new String

&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!