Introduction to Rust

    2024-10-03 (last edit: 2024-09-20)

    Logo

    A language empowering everyone to build reliable and efficient software.

    (unofficial logo)

    Why use Rust?

    • It is safe (compared to C++ for example, as we will see in a minute)
    • It is fast (because it is compiled to machine code)
    • It is ergonomic and pleasant to use (static typing, expressive type system, helpful compiler warnings)
    • It is loved by programmers
    • It provides excellent tooling

    Why learn Rust?

    Even if you don't end up using Rust, learning it expands your horizons

    • it helps especially with the awareness of what you can and can't do in concurrent applications
    • it helps you understand memory management and learn its good practices

    Why not to learn Rust?

    • Some people say Rust is too hard to learn because of the borrow checker
    • Once you get to know Cargo you won't ever want to use a language without a built-in package manager ;)
    • You will start hating C++

    Demos

    Meme

    Let's compare the same code written in C, C++ and Rust.

    Code you sent in previous editions!

    Aleksander Tudruj

    #include <iostream>
    #include <unordered_map>
    
    using name = std::string;
    using age = int;
    using person = std::pair<name, age>;
    using address = std::string;
    using address_book = std::unordered_map<person, address>;
    
    void print_address_book(const address_book &book)
    {
        for (const auto &[person, address] : book)
        {
            std::cout << person.first << " is " << person.second << " years old and lives at " << address << std::endl;
        }
    }
    
    int main()
    {
    
        address_book people{};
        people.insert({{"John", 20}, "221B Baker Street, London"});
        people.insert({{"Mary", 30}, "Avenue des Champs-Élysées, Paris"});
        people.insert({{"Jack", 73}, "Wall Street, New York"});
        print_address_book(people);
    
        return 0;
    }
    
    

    (Download the source code for this example: tudruj.cpp)

    Krystyna Gasińska

    # sample 1 - different ways of removing elements from the list while iterating
    list1 = [1, 2, 3, 4]
    for idx, item in enumerate(list1):
        del item
    list1
    
    # [1, 2, 3, 4]
    
    list2 = [1, 2, 3, 4]
    for idx, item in enumerate(list2):
        list2.remove(item)
    list2
    
    # [2, 4]
    
    list3 = [1, 2, 3, 4]
    for idx, item in enumerate(list3[:]):
        list3.remove(item)
    list3
    
    # []
    
    list4 = [1, 2, 3, 4]
    for idx, item in enumerate(list4):
        list4.pop(idx)
    list4
    
    # [2, 4]
    
    # sample 2 - string interning
    a = "abc"
    b = "abc"
    a is b
    
    # True
    
    a = ''.join(['a', 'b', 'c'])
    b = ''.join(['a', 'b', 'c'])
    a is b
    
    # False
    
    a = "abc!"
    b = "abc!"
    a is b
    
    # False
    
    # sample 3 - chained operations
    (False == False) in [False]
    
    # False
    
    False == (False in [False])
    
    # False
    
    False == False in [False] # unexpected...
    
    # True
    
    # sample 4 - is operator
    a = 256
    b = 256
    a is b
    
    # True
    
    a = 257
    b = 257
    a is b
    
    # False
    
    a, b = 257, 257
    a is b
    
    # True
    
    257 is 257
    
    # <>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
    # <>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
    # C:\Users\kgasinsk\AppData\Local\Temp\ipykernel_15776\331119389.py:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
    #  257 is 257
    
    # sample 5 - local variables
    def f(trufel):
        if trufel:
            y = 1
        y += 1
    
    f(True) # everything is fine
    
    f(False) # gives error: local variable 'y' referenced before assignment
    
    # ---------------------------------------------------------------------------
    # UnboundLocalError                         Traceback (most recent call last)
    # Input In [17], in <cell line: 1>()
    # ----> 1 f(False)
    
    # Input In [15], in f(trufel)
    #       3 if trufel:
    #       4     y = 1
    # ----> 5 y += 1
    
    # UnboundLocalError: local variable 'y' referenced before assignment
    

    (Download the source code for this example: gasinska.py)

    Antoni Koszowski

    // mutowalność jest wbudowana w język
    
    type S struct {
        A string
        B []string
    }
     
    func main() {
        x := S{"x-A", []string{"x-B"}}
        y := x // copy the struct
        y.A = "y-A"
        y.B[0] = "y-B"
     
        fmt.Println(x, y)
        // Outputs "{x-A [y-B]} {y-A [y-B]}" -- x was modified!
    }
    
    // slices i kwestia append
    
    func doStuff(value []string) {
        fmt.Printf("value=%v\n", value)
     
        value2 := value[:]
        value2 = append(value2, "b")
        fmt.Printf("value=%v, value2=%v\n", value, value2)
     
        value2[0] = "z"
        fmt.Printf("value=%v, value2=%v\n", value, value2)
    }
     
    func main() {
        slice1 := []string{"a"} // length 1, capacity 1
     
        doStuff(slice1)
        // Output:
        // value=[a] -- ok
        // value=[a], value2=[a b] -- ok: value unchanged, value2 updated
        // value=[a], value2=[z b] -- ok: value unchanged, value2 updated
     
        slice10 := make([]string, 1, 10) // length 1, capacity 10
        slice10[0] = "a"
     
        doStuff(slice10)
        // Output:
        // value=[a] -- ok
        // value=[a], value2=[a b] -- ok: value unchanged, value2 updated
        // value=[z], value2=[z b] -- WTF?!? value changed???
    }
    
    // error handling
    
    len, err := reader.Read(bytes)
    if err != nil {
        if err == io.EOF {
            // All good, end of file
        } else {
            return err
        }
    }
    
    
    // interfejs nil
    
    type Explodes interface {
        Bang()
        Boom()
    }
     
    // Type Bomb implements Explodes
    type Bomb struct {}
    func (*Bomb) Bang() {}
    func (Bomb) Boom() {}
     
    func main() {
        var bomb *Bomb = nil
        var explodes Explodes = bomb
        println(bomb, explodes) // '0x0 (0x10a7060,0x0)'
        if explodes != nil {
            println("Not nil!") // 'Not nil!' What are we doing here?!?!
            explodes.Bang()     // works fine
            explodes.Boom()     // panic: value method main.Bomb.Boom called using nil *Bomb pointer
        } else {
            println("nil!")     // why don't we end up here?
        }
    }
    
    // ubogie struktury danych, takie customowe tracą type safety m.in poprzez castowanie do interface{}
    // kiedyś brak generyków, choć teraz w znacznym stopniu problem został rozwiązany.
    

    (Download the source code for this example: koszowski.go)

    Mieszko Grodzicki

    def add_contents(input_list, contents=[]):
         for val in input_list:
             contents.append(val)
         return contents
    
    print(add_contents([1])) # [1]
    print(add_contents([2])) # [1, 2]
    

    (Download the source code for this example: grodzicki.py)

    Installing Rust

    Useful tools

    Clippy

    • cargo clippy (for static analysis)
    • there's also cargo check, but it's less powerful than clippy
    • cargo fmt (for code formatting)

    Rust Playground

    Hello world

    fn main() {
        let name = "World";
        println!("Hello, {}!", name); // using the println! macro
    }
    
    

    (Download the source code for this example: hello_world.rs)

    Variables

    #![allow(unused_variables)]
    #![allow(unused_assignments)]
    
    fn main() {
        let x = 40; // inferred type
        let y: i32 = 100; // specified type
    
        {
            let x = 40 + 2; // shadowing
            println!("x is {}", x); // prints 42
        }
    
        // x = 0; // compilation error, variables are by default immutable
        let mut x = 40; // declare as mutable
        x = 0; // now we can reassign
    
        x += 1; // x = x + 1
    }
    
    

    (Download the source code for this example: variables.rs)

    Conditionals

    #![allow(unused_variables)]
    
    fn main() {
        let x = 42;
    
        if x == 42 {
            println!("x is 42");
        } else if x == 43 {
            println!("x is 43");
        } else {
            println!("x is not 42 or 43");
        }
    
        // we can also use ifs as expressions
        let a_or_b = if x == 0 {
            "a" // notice no semicolon at the end
        } else {
            "b"
        };
    }
    
    

    (Download the source code for this example: conditionals.rs)

    Loops

    #![allow(unused_variables)]
    
    fn main() {
        for i in 0..10 {
            println!("i is {}", i); // i in [0, 10)
        }
    
        let mut x = 0;
    
        while x < 50 {
            x += 1;
        }
    
        let mut y = 0;
        let mut iterations = 0;
        loop {
            iterations += 1;
            if iterations % 2 == 0 {
                continue;
            }
            y += 1;
            if y == 10 {
                break;
            }
        }
    
        // we can use labels to refer to a specific loop
        let mut count = 0;
        'counting_up: loop {
            let mut remaining = 10;
    
            loop {
                if remaining == 9 {
                    break;
                }
                if count == 2 {
                    break 'counting_up; // ends the outer loop
                }
                remaining -= 1;
            }
    
            count += 1;
        }
    
        // We can use break with a value.
        // Because loops are expressions too,
        // the value we break with will be returned from the functions
        let mut counter = 0;
        let value = loop {
            counter += 1;
            if counter == 10 {
                break 32;
            }
        };
    }
    
    

    (Download the source code for this example: loops.rs)

    Functions

    fn get_5() -> u32 {
        5 // we could also write "return 5;"
    }
    
    fn print_sum(a: u32, b: u32) {
        println!("a + b = {}", a + b);
    }
    
    fn main() {
        let a = 100;
        print_sum(a, get_5());
    }
    
    

    (Download the source code for this example: functions.rs)

    Test assignment (not graded)

    Click here

    Obligatory reading

    Additional reading