Common Concepts

Contains some of the basic general concepts of programming languages and how they apply to Rust.

Variables

Mutability

Variables are immutable by default. This means once a value is bound to the variable name, the value can’t be changed. To make variables mutable the mut keyword has to be added after the let keyword, when instancing a variable.

let mut x : i8 = 5;

Constants

Variables that are immutable and can’t be mutable, use the const keyword instead of let and the type of the value needs to be annotated.

Can be declared in any scope even the global one and can be set only to a constant expression, that evaluates at compile time not runt time.

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

Shadowing

Allows using the same variable name to use that variable instead of the first one, effectively “shadowing” it. Additionally allows changing the type which is not possible when using a mutable variable.

let spaces = "   "; 
let spaces = spaces.len(); 

Data Types

Rust ist statitically typed, meaning all types of variable must be known at compile time. They can be inferred by the compiler most of the time, but it sometimes needs a type annotation.

let guess: u32 = "42".parse().expect("Not a number!");

Scalar Types

Represents a single value: integers, floating-point numbers, Booleans, and characters.

Integer Types

Number without fractional component. Signed / unsigned refers to whether the number can represent negative values.

n: Represents the amount of bits that variant uses

Signed size: -(2^n−1) to (2^n−1) - 1

Unsigned size: 0 to (2^n) - 1

LENGTH SIGNED UNSIGNED
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch1 isize usize
NUMBER LITERALS EXAMPLE
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte (u8 only) b'A'

Integer overflows are detect at runtime by rust in debug mode and will result in a panic integer overflow.

Floating Types

Numbers with decimal points. All floating-point types are signed and a floating-point number is 64-bit per default.

LENGTH SIGNED
32-bit f32
64-bit f64

Boolean Type

Two possible values true and false, 1-bit in size. Defined with bool.

Character Type

Defined with char, 32-bit in size. char literals are specified 'c', opposed to string literals, which use "string". Represents a Unicode scalar value, range from U+0000 to U+D7FF and U+E000 to U+10FFFF inclusive.

Compound Types

Can group multiple values into one type.

Tuple Type

General way of grouping together number of values with variety of types, have a fixed length (once declared they cannot shrink or grow in size).

let tup: (i32, f64, u8) = (500, 6.4, 1); 

Single element can be accessed directly with their given index.

let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;

Tuple without any value is called unit, this values and type are both written as (). Expressions implicitly return the unit value if they don’t return any other value.

Pattern Matching

The individual values from the tuple can be destructured with pattern matching.

let (x, y, z) = tup;

Array Type

Every element has to have the same type and the array has a fixed size. To type annotate we first enter the type in square brackets followed by a semicolon and the size.

let a: [i32; 5] = [1, 2, 3, 4, 5];

To initialize an array with the same value, we can first enter the value and then after a semicolon the amount of items that should be created with that value in square brackets.

let a = [3; 5];
Accessing

Element can be accessed using indexing.

let first = a[0];
let second = a[1];

Accessing an index past the actual size is checked at runtime by rust and will result in a panic if detected with index out of bounds.

Functions

Uses fn keyword to declare a new function and uses snake case as conventional style.

fn another_function() {
    println!("Another function.");
}

Curly brackets tell the compiler, where function body begins and ends.

Parameters

Functions can have parameters which are special variables that are part of the signature. It can then be provided with arguments for those variables.

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

Type of each parameter needs to be declared in a function signature.

Statements / Expressions

Statements are instructions that perform some action and do not return a value.

let y = 6;

Expression evaluate to a resulting value.

let x = 3;
x + 1

New scope block, calling a macro, calling a function are all expressions. If you add a semicolon to the end of an expression it turns into a statement and will not return a value.

let y = {
    let x = 3;
    x + 1
};

Functions with Return Values

Return values must have their type declared after an arrow ->.

fn five() -> i32 {
    5 
}

5 in this context is the same as return 5;

Comments

Additional text to describe code.

Single Line:

// Example

Multi Line:

/*
 * Example
 */

Control Flow

Ability to run some code (repeatedly) depending on if a condition is true or not.

If Expressions

Allows to branch code depending on conditions.

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

Handling Multiple Conditions with else if

Multiple conditions can be used by combining if and else in else if expression.

If more than one else if is used, a match expression might be useful.

Using if in let Statement

If is an expression and can therefore be assigned to a variable.

let number = if condition { 5 } else { 6 };

let number = if condition { 5 } else { "six" };

Evaluates to error

Repetition with Loops

Executes a block more than once, loop, while, for.

Loop

Executes a block of code over and over again forever or until explicitly stopped.

Returning values

Loops are expression and can therefore return expression, simply add a semicolon after the loop brackets.

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

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

    println!("The result is {result}");
}
Loop Labels

Loops withing loops, mean break and continue apply only to the innermost loop.

Optionally a loop label can be specified, that can be used with break or continue to specify which loop they apply to. Start with single quote.

'counting_up: loop {
    loop {
        break 'counting_up;
    }
}
Conditional Loops

Evaluate a condition withing a loop, while the condition is true the loop runs. When condition ceases to be true, the program calls break.

Built-in language construct while loop:

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");
        number -= 1;
    }
    println!("LIFTOFF!!!");
}
Looping Through Collections

Loop through a collection and accessing all of its elements. Built-in language construct for loop.

let a = [10, 20, 30, 40, 50];

for element in a {
    println!("the value is: {element}");
}

for number in (1..4).rev() {
    println!("{number}!");

}
println!("LIFTOFF!!!");
Getting both Index and Element

Using arr.iter().enumerate(), we can get a tuple with both the index and the reference to the element.

for (i, e) in a.iter().enumerate() {
    println!("Index: {i}, Value: {e}");
}
  1. Used architecture of pc 32-bit or 64-bit