These are some notes I took while following the book The Rust Programming Language.
- 1. MAJP's notes on Rust
- 2. Mutability
- 3. Constants
- 4. Shadowing
- 5. Data types
- Expressions and statements
- Inline logic
- Loop with return value
- Labelling loops
If a variable is immutable (default), its value cannot change. You cannot shadow the variable either. This would produce a compilation error:
fn main {
let x = 5;
x = 1;
}
But this would not:
fn main {
let mut x = 5;
x = 1;
}
Constants are like immutable variables, but there are some differences:
- You cannot use
mut
with a constant. - The value must be annotated.
- Constants can be declared in any scope, including the global scope.
- Constants can only be declared as constant expressions. They cannot come from an expression whose result is only available at runtime.
This is a constant:
const NUMBER_OF_SECONDS_IN_A_MINUTE: u8 = 60;
Rust is able to evaluate a number of expressions at compile time, so the following would also work:
const APPROXIMATELY_PI: f32 = 22 / 7;
In the following expression, the first variable is shadowed by the second one:
let x = 1
let x = 2
To reiterate, shadowing and mutability are not the same. For a immutable variable, we can only shadow using let
.
We can have pseudo-dynamic typing by using then let
keyword like this:
let characters = "abc"; // Is a String
let characters = characters.len(); // Is a u32
But this wouldn't compile:
let mut characters = "abc"
characters = characters.len() // will fail due to change in type
Shadowing only happens in the current scope.
Here is an example:
fn main() {
let x = 5;
println!("{x}");
{
let x = x * 2;
println!("{x}");
}
println!("{x}");
}
Output:
5
10
5
Rust is statically typed, so it must know the types of all variables. Often the compiler can infer the type. Sometimes it can't, so we have to use annotations.
There are four primary scalar types in Rust:
- Integers
- Floating-point numbers
- Booleans
- Characters
As known from other languages (u8
, i8
, u128
and so on). Rust also has the arch
type, annotated with isize
and usize
, which refers to the OS architecture (32 or 64 bit).
You can write an integer like
let my_int = 100;
Which defaults to i32
, or you can write it like
let my_int = 100i8;
To force the u8
type.
Integer overflow occrs when the size of an integer is outside its tyep's range. Rust has some deterministic but perhaps unexpected behaviour here when compiling in --release
mode. See here.
###2.4.2. Compound types
Rust has types that consist of several other types. An example is the tuple
:
let tup: (i32, f64, bool) = (500, 3.14, true);
We can destructure a tuple like this:
let tup: (i32, f64, bool) = (500, 3.14, true);
let (x, y, z) = tup;
We can also use dot indexing like this:
let x = tup.0;
let y = tup.1;
let z = tup.2;
Arrays in Rust are collections of multiple values. Contrary to tuples, values in an array must have the same type. Here is an array:
let arr: [i8; 4] = [1, 2, 3, 4]; // Array of u8s with 4 elements
Arrays in Rust have a fixed length.
You can "fill" an array with the same value like this:
let zeros = [0; 5]; // Array with five zeros
You can index into an array like this:
let arr = [1, 2, 3];
let three = arr[2];
Vectors are like arrays, but they can change in size. You can make a vector like this:
let mut vec = Vec::new();
or like this:
let mut vec = vec![1, 2, 3];
Expressions and statements are different things. An expression has a return value and a statement does not.
let x = 1; // Statement
let y = {
let z = 5
z + 2
}; // Expression (y = 7)
Note that the z + 2
line does not end with ;
. Expressions don't end with semicolons. This makes them return a value.
We can use inline logic for assignments like this:
let boolean = true;
let x = if boolean { 5 } else { 6 }; // Will evaluate to 5 because boolean is true.
We can make a loop expression that returns a value like this
let mut i = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
}
Here, result
will have the value 20.
We can label a loop to give it a name like this:
let mut i = 0;
let mut j = 0;
'outer_loop: loop {
i += 1;
'inner_loop: loop {
j += 2;
if j == 52 {
break;
}
if i == 10 {
break 'outer_loop;
}
}
}
This will break the outer loop if i == 10
and break the inner loop if j == 52
.