Day 1: Basic Rust, ownership and the borrow checker.

Basic Rust

  • rustc uses LLVM

  • Multiple architectures (WASM included)

    • cargo build --target wasm32-unknown-unknown
  • Basic rust syntax:

    • Variables,
    • Scalar and compound types,
    • Enums,
    • Structs References,
    • Functions and Methods
  • Memory management:

    • stack vs heap,
    • manual memory management,
    • scope based memory management and gc
  • Ownership:

    • move semantics,
    • copying and cloning,
    • borrowing and lifetimes

Syntax sugar

  • Blocks are delimited by curly braces
  • main is the entry point
  • Provides hygienic macros and utf-8 encoded and contain unicode

Types

Scalar types

TypesLiterals
Signed integersi8, i16, i32, i64, i128, isize-10, 0, 1_000, 123i64
Unsigned integersu8, u16, u32, u64, u128, usize0, 123, 10u16
Floating point numbersf32, f643.14, -10.0e20, 2f32
Strings&str"foo", r#"\\"#
Unicode scalar valueschar'a', 'α', '∞'
Byte strings&[u8]b"abc", br#" " "#
Booleansbooltrue, false

Compound Types

TypesLiterals
Arrays[T; N][20, 30, 40], [0; 3]
Tuples(T1, T2, T3, ...)('x', 1.2, 0)

Other data structures exists:

Variables

mut is used when a variable is initialized and needs to be changed later. ! marks this as a macro invocation, not a function call. let statements declare local variables

fn function1() -> i32 {
    let _logical: bool = true;
    let _a_float: f64 = 1.0;
    let _an_integer = 5i32;

    let _default_int = 2; // i32

    let mut mutablevar = 12;
    mutablevar = 35;

    return mutablevar;
}

fn main() {
    let out = function1();
    println!("{}", out);
}

Pointers

Similar to golang and pointers are treated with references and &

use rand::Rng;
use std::cmp::Ordering;

fn main() {
    let guess :i32 = 10;
    let secret_number :&i32 = &rand::thread_rng().gen_range(1..=10);

    match guess.cmp(secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
    
    println!("{}", *secret_number)
}

The main aim of lifetimes is to prevent dangling references, which cause a program to reference data other than the data it’s intended to reference.

fn main() {
    let ref_x: &i32;
    {
        let x: i32 = 10;
        ref_x = &x;
    }
    println!("ref_x: {ref_x}");
}

Won't compile because the value ref_x is referring to has gone out of scope before we try to use it.

Slices

fn main() {
  let s = String::from("hello world");

  let hello = &s[0..5];
  let world = &s[6..s.len()];
  
  println!("slices: {}, {}", hello, world)
}

Functions and methods

  • Functions are introduced with fn (pronounced "fun")
  • Methods comes from structs
struct Square {
    width: i32
}

impl Square {
    fn area(&self) -> i32 {
        return i32::pow(self.width, 2)
    }
}

fn main() {
    let sq :Square = Square{width: 10};
    let area :i32 = sq.area();
    println!("{area}");
}

Exercises

A few implicit conversions:

#![allow(unused)]
fn main() {
println!("{x} * {y} = {} -- {} {}", multiply(i16::from(x), y), y.to_string(), f64::from(y));
}

Array loops, matrices transposition.

fn transpose(mut matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
    // returning cloned transpose for now. requires research on borrowing concept.
    let mut new_matrix:[[i32; 3]; 3] = matrix.clone();
    for (i, r) in matrix.iter().enumerate() {
        for (j, _c) in r.iter().enumerate() {
            new_matrix[i][j] = matrix[j][i];
        }
    }
    return new_matrix
}

fn pretty_print(matrix: &[[i32; 3]; 3]) {
    println!("{:?}", matrix);
}

fn main() {
    let matrix = [
        [101, 102, 103], // <-- the comment makes rustfmt add a newline
        [201, 202, 203],
        [301, 302, 303],
    ];

    println!("matrix:");
    pretty_print(&matrix);

    let transposed = transpose(matrix);
    println!("transposed:");
    pretty_print(&transposed);
}