magical markdown slides

theme: default author: Learn Rust#

Learn Rust#

A quick tour through Rust fundamentals


Comments#

Rust supports multiple comment styles:

// Line comments look like this
// and extend multiple lines

/* Block comments
   /* can be nested. */ */

/// Documentation comments support markdown
/// # Examples
/// ```
/// let five = 5
/// ```

Functions#

Functions use fn keyword with type annotations:

fn add2(x: i32, y: i32) -> i32 {
    // Implicit return (no semicolon)
    x + y
}

Key points:

  • i32 is a 32-bit signed integer
  • Last expression without ; is the return value
  • Function parameters must have type annotations

Variables#

Rust has immutable bindings by default:

// Immutable binding
let x: i32 = 1;

// Type inference works most of the time
let implicit_x = 1;
let implicit_f = 1.3;

// Mutable variable
let mut mutable = 1;
mutable = 4;
mutable += 2;
TIP

Variables are immutable by default - use mut only when you need to change values


Numbers#

Integer and float types with suffixes:

// Integer/float suffixes
let y: i32 = 13i32;
let f: f64 = 1.3f64;

// Arithmetic
let sum = x + y + 13;

Strings#

Two main string types in Rust:

// String slice (&str) - immutable view
let x: &str = "hello world!";

// String - heap-allocated, growable
let s: String = "hello world".to_string();

// String slice from String
let s_slice: &str = &s;

// Printing
println!("{} {}", f, x);

Arrays and Vectors#

Fixed-size arrays and dynamic vectors:

// Fixed-size array
let four_ints: [i32; 4] = [1, 2, 3, 4];

// Dynamic vector
let mut vector: Vec<i32> = vec![1, 2, 3, 4];
vector.push(5);

// Slice - immutable view
let slice: &[i32] = &vector;

// Debug printing
println!("{:?} {:?}", vector, slice);

Tuples#

Fixed-size sets of values of possibly different types:

// Tuple declaration
let x: (i32, &str, f64) = (1, "hello", 3.4);

// Destructuring
let (a, b, c) = x;
println!("{} {} {}", a, b, c); // 1 hello 3.4

// Indexing
println!("{}", x.1); // hello

Structs#

Custom data types with named fields:

struct Point {
    x: i32,
    y: i32,
}

let origin: Point = Point { x: 0, y: 0 };

// Tuple struct (unnamed fields)
struct Point2(i32, i32);
let origin2 = Point2(0, 0);

Enums#

Enums can have variants with or without data:

// Basic C-like enum
enum Direction {
    Left,
    Right,
    Up,
    Down,
}

let up = Direction::Up;

// Enum with fields
enum OptionalI32 {
    AnI32(i32),
    Nothing,
}

let two: OptionalI32 = OptionalI32::AnI32(2);

Generics#

Type parameters for reusable code:

struct Foo<T> { bar: T }

enum Optional<T> {
    SomeVal(T),
    NoVal,
}

The standard library provides Option<T> for optional values, replacing null pointers.


Methods#

Functions associated with types:

impl<T> Foo<T> {
    // Borrowed self
    fn bar(&self) -> &T {
        &self.bar
    }

    // Mutably borrowed self
    fn bar_mut(&mut self) -> &mut T {
        &mut self.bar
    }

    // Consumed self
    fn into_bar(self) -> T {
        self.bar
    }
}

Traits#

Interfaces that define shared behavior:

trait Frobnicate<T> {
    fn frobnicate(self) -> Option<T>;
}

impl<T> Frobnicate<T> for Foo<T> {
    fn frobnicate(self) -> Option<T> {
        Some(self.bar)
    }
}

let foo = Foo { bar: 1 };
println!("{:?}", foo.frobnicate()); // Some(1)

Pattern Matching#

Powerful control flow with match:

let foo = OptionalI32::AnI32(1);
match foo {
    OptionalI32::AnI32(n) => println!("it's an i32: {}", n),
    OptionalI32::Nothing  => println!("it's nothing!"),
}

Advanced Pattern Matching#

Destructure and use guards:

struct FooBar { x: i32, y: OptionalI32 }
let bar = FooBar { x: 15, y: OptionalI32::AnI32(32) };

match bar {
    FooBar { x: 0, y: OptionalI32::AnI32(0) } =>
        println!("The numbers are zero!"),
    FooBar { x: n, y: OptionalI32::AnI32(m) } if n == m =>
        println!("The numbers are the same"),
    FooBar { x: n, y: OptionalI32::AnI32(m) } =>
        println!("Different numbers: {} {}", n, m),
    FooBar { x: _, y: OptionalI32::Nothing } =>
        println!("The second number is Nothing!"),
}

For Loops#

Iterate over arrays and ranges:

// Array iteration
let array = [1, 2, 3];
for i in array {
    println!("{}", i);
}

// Range iteration
for i in 0u32..10 {
    print!("{} ", i);
}
// prints: 0 1 2 3 4 5 6 7 8 9

If Expressions#

if can be used as an expression:

if 1 == 1 {
    println!("Maths is working!");
} else {
    println!("Oh no...");
}

// if as expression
let value = if true {
    "good"
} else {
    "bad"
};

Loops#

Multiple loop constructs:

// while loop
while condition {
    println!("Looping...");
    break  // Exit the loop
}

// Infinite loop
loop {
    println!("Hello!");
    break  // Must break explicitly
}

Owned Pointers (Box)#

Box<T> provides heap allocation with single ownership:

let mut mine: Box<i32> = Box::new(3);
*mine = 5; // dereference

// Ownership transfer (move)
let mut now_its_mine = mine;
*now_its_mine += 2;

println!("{}", now_its_mine); // 7
// println!("{}", mine); // Error! moved

When Box goes out of scope, memory is automatically deallocated.


Immutable References#

Borrowing without transferring ownership:

let mut var = 4;
var = 3;
let ref_var: &i32 = &var;

println!("{}", var);      // Still works!
println!("{}", *ref_var); // 3

// var = 5;      // Error! var is borrowed
// *ref_var = 6; // Error! immutable reference

ref_var; // Use the reference
var = 2; // Borrow ended, can mutate again

Mutable References#

Exclusive mutable access:

let mut var2 = 4;
let ref_var2: &mut i32 = &mut var2;
*ref_var2 += 2;

println!("{}", *ref_var2); // 6

// var2 = 2; // Error! var2 is mutably borrowed

ref_var2; // Use ends here
// Now var2 can be used again
WARNING

Only ONE mutable reference OR many immutable references at a time


Memory Safety#

IMPORTANT

Rust's borrow checker ensures memory safety at compile time

Guarantees:

  • No use after free
  • No double free
  • No data races
  • No dangling pointers

All at compile time with zero runtime cost.


Next Steps#


Thank You#

Happy Rusting!