website/content/slides/rust-introduction.md
2024-04-13 15:26:52 +02:00

11 KiB
Raw Permalink Blame History

title date
Rust introduction 2023-02-10T13:51:58+01:00

Introduction to...

Rust


Introduction


Types


Primitives

  • signed ints: i8, i16, i32, i64, i128, isize
  • unsigned ints: u8, u16, u32, u64, u128, usize
  • floating point: f32, f64
  • char: Unicode scalar
  • bool: true, false
  • unit: ()

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

Compound

Name Type Example value
Array [3; i32] [1, 2, 3]
Tuple (i32, bool) (1, true)

Enum

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

enum Coin {
    Penny,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

Option & Result

enum Option<T> {
    None,
    Some(T),
}

enum Result<T, E> {
   Ok(T),
   Err(E),
}

Struct

struct Container {
    a: i32,
    b: i32,
}

impl Container {
    fn sum_ab(&self) -> i32 {
        self.a + self.b
    }
}

fn main() {
    let container = Container {
        a: 10,
        b: 15,
    };

    let sum = container.sum_ab();
}

Vector

A contiguous growable array type, written as Vec<T>, short for vector.


let mut vec = Vec::new();
vec.push(1);
vec.push(2);

assert_eq!(vec.len(), 2);
assert_eq!(vec[0], 1);

assert_eq!(vec.pop(), Some(2));
assert_eq!(vec.len(), 1);

vec[0] = 7;
assert_eq!(vec[0], 7);

The vec! macro is provided for convenient initialization:

let mut vec1 = vec![1, 2, 3];
vec1.push(4);
let vec2 = Vec::from([1, 2, 3, 4]);
assert_eq!(vec1, vec2);

Control flow


if

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

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

loop

fn main() {
    loop {
        println!("again!");
    }
}

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

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

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

while

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

for

fn main() {
    let a = [10, 20, 30, 40, 50];

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

Iterators

JS:

myList
  .filter((t) => t !== "Filter me out")
  .forEach(console.log);

Rust:

myList.into_iter()
  .filter(|v| v != "Filter me out")
  .for_each(|v| println!("{}", v));

Funtion What it does
.take(n) reduces an iterator to its first n elements
.skip(n) skips the first n elements
.cloned() clones each element in the iterator
.enumerate turns an iterator over elements t to an iterator over elements (t: T, idx: usize)
.cycle loops the iterator infinitely
.rev reverses an iterator

Attributes


// A function marked as a unit test
#[test]
fn test_foo() {
    /* ... */
}

// A conditionally-compiled module
#[cfg(target_os = "linux")]
mod bar {
    /* ... */
}

// A lint attribute used to suppress a warning/error
#[allow(non_camel_case_types)]
type int8_t = i8;

// Inline function, when compiling to machine code
#[inline(always)]
fn siema() {
    /* ... */
}

Ownership


struct Container {
    a: i32,
    b: i32,
}

fn consume(container: Container) -> i32 {
    container.a + container.b
}

fn borrow(container: &Container) -> i32 {
    container.a + container.b
}

fn borrow_mut(container: &mut Container) -> i32 {
    container.a + container.b
}

fn main() {
    let c1 = Container { a: 10, b: 15 };
    let c2 = Container { ..c1 };

    let sum1 = consume(c1);
    // let sum1 = consume(c1); // illegal

    let sum2 = borrow(&c2);
    let sum2 = borrow(&c2); // ok

    let mut c3 = Container { ..c2 };

    let sum3 = borrow_mut(&mut c3);
    let sum3 = borrow_mut(&mut c3); // ok
}

Borrow checker

compiler error


Mutable borrow

fn main() {
    // --snip--

    let mut c3 = Container { ..c2 };

    // ok
    let ref1 = &c3;
    let ref2 = &c3;

    // ok
    let ref1 = &mut c3;
    ref1.a;
    // compiler knows the first borrow can be dropped here
    let ref2 = &mut c3;

    // illegal
    let ref1 = &mut c3;
    let ref2 = &mut c3;
    ref1.a;
}

Borrow checker

compiler error


Lifetimes


fn test(arr: &[i32]) -> impl Iterator<Item=&i32> {
    return  arr.iter().cycle()
}

fn main() { // works
    let x = vec![1, 2, 3];
    let x = test(&x);
    x.take(9).for_each(|i| print!("{i}") );
}

fn main() { // doesn't work
    let x = test(&vec![1, 2, 3]);
    x.take(9).for_each(|i| print!("{i}") );
}

borrow cheecker


fn borrow(c: &Container) {
    // -- snip --
}

fn borrow<'a>(c: &'a Container) {
    // -- snip -- 
}

fn main() {
    let i = 3; // Lifetime for `i` starts. ────────────────┐
    //                                                     │
    { //                                                   │
        let borrow1 = &i; // `borrow1` lifetime starts. ──┐│
        //                                                ││
        println!("borrow1: {}", borrow1); //              ││
    } // `borrow1` ends. ─────────────────────────────────┘│
    //                                                     │
    //                                                     │
    { //                                                   │
        let borrow2 = &i; // `borrow2` lifetime starts. ──┐│
        //                                                ││
        println!("borrow2: {}", borrow2); //              ││
    } // `borrow2` ends. ─────────────────────────────────┘│
    //                                                     │
}   // Lifetime ends. ─────────────────────────────────────┘

struct Container<'a> {
    x: &'a i32,
    y: &'a i32,
}

fn main() {
    let x = 1;
    let v;
    {
        let y = 2;
        let f = Container { x: &x, y: &y };
        v = f.x;
    }
    println!("{}", *v);
}

borrow checker


struct Container<'a, 'b> {
    x: &'a i32,
    y: &'b i32,
}

Day 21:

fn chain_eval<'data, 'a>(
    start: &'data str,
    dependees: &'a HashMap<&'data str, Vec<&'data str>>,
    awaiting: &'a mut HashMap<&'data str, Shout>,
    finished: &'a mut HashMap<&'data str, i64>,
) {
    // -- snip --
}

pub fn run() -> () {
    // 'data lifetime start
    let data = utils::read_lines(utils::Source::Day(21)); 
    let data = parse_data(&data);

    println!("Day 21");
    println!("Part 1: {}", solve1(&data));
    println!("Part 2: {}", solve2(&data));
}

fn test(arr: &[i32]) -> impl Iterator<Item=&i32> {
    return  arr.iter().cycle()
}

fn main() { // doesn't work
    let x = test(&vec![1, 2, 3]);
    x.take(9).for_each(|i| print!("{i}") );
}

fn main() { // works
    let x = test(&[1, 2, 3]);
    x.take(9).for_each(|i| print!("{i}") );
}

fn test(arr: &[i32]) -> impl Iterator<Item=&i32> {
    return  arr.iter().cycle()
}

fn main() {
    let x = {
        let x: &'static [i32] = &[1, 2, 3];
        test(&x)
    };
    x.take(9).for_each(|i| print!("{i}") );
}

Unsafe


unsafe fn test(n: i32) -> i32 {
    // do something unsafe
    n * 5
}

fn test2() -> i32 {
    unsafe { test(4) }
}

fn main() {
    let x = unsafe { test(4) };
    let x = test2();
}

Traits

Haskell:

class Cool a where
    giveMeNumber :: a -> Integer

Rust:

trait Cool {
    fn give_me_number(&self) -> i32;
}

Haskell:

newtype Hello = Hello Integer

instance Cool Hello where
  giveMeNumber (Hello n) = n * 10

Rust:

struct Hello(i32);

impl Cool for Hello {
    fn give_me_number(&self) -> i32 { self.0 * 10 }
}

Haskell:

main = do
  let test = Hello 2
  putStrLn $ show $ giveMeNumber test

Rust:

fn main() {
    let test = Hello(2);
    println!("{}", test.give_me_number())
}

Some built in traits:

  • ToString
  • Display and Debug
  • Default
  • From and Into
  • Clone and Copy
  • Deref
  • Iterator and IntoIterator

Default

#[derive(Default, Debug)]
struct Hello { a: i32, b: i32, c: Vec<i32> }

fn main() {
    let x = Hello { a: 22, ..Default::default() };
    println!("{x:?}")
}
Hello { a: 22, b: 0, c: [] }

Sized

// core::marker::Sized
pub trait Sized { }
fn test() -> dyn Iterator<Item=i32> {
    [1, 2, 3].into_iter()
}

compiler


fn test() -> Box<dyn Iterator<Item=i32>> + Sized {
    Box::from([1, 2, 3].into_iter())
}

Send & Sync

  • A type is Send if it is safe to send it to another thread.
  • A type is Sync if it is safe to share between threads (T is Sync if and only if &T is Send).

?

fn test() -> Option<i32> {
    let mut test: Vec<i32> = vec![1, 2, 3];
    let x = test.pop()?;
    let y = test.pop()?.checked_add(22)?;
    Some(x * y)
}

fn main() {
    println!("{}", test().unwrap())
}