100 Exercises to Learn Rust

repository·main·Indexed 27 days ago

https://github.com/mainmatter/100-exercises-to-learn-rust

A structured learning path consisting of 100 exercises designed to take developers from zero Rust knowledge to being able to write their own programs. The course covers fundamental Rust syntax and concepts, including functions, variables, primitive types, arithmetic operators, control flow, and panics. It includes a companion GitHub repository and an optional Workshop Runner (wr) CLI tool to guide users and verify solutions.

Tokens
54.8K
Snippets
220
Records
416
Agent score
93%

What's inside 100-exercises-to-learn-rust

  1. Introduction to Async Rust concepts

    main

    This chapter introduces asynchronous programming in Rust as an alternative to thread-based concurrency. You will learn about:

    • async/.await keywords: Used to write asynchronous code with a syntax similar to synchronous code.
    • The Future trait: A trait representing a computation that may not have completed yet.
    • tokio: The industry-standard runtime used to execute asynchronous code.
    • Cooperative multitasking: Understanding how Rust's asynchronous model requires tasks to yield control, affecting how you structure your code.
  2. Overview of the Basic Calculator chapter

    main

    The Basic Calculator chapter uses calculator-based exercises to teach fundamental Rust syntax and concepts. By completing these exercises, you will learn:

    • Defining and calling functions
    • Declaring and using variables
    • Using primitive types (integers and booleans)
    • Using arithmetic operators (including understanding overflow and underflow behavior)
    • Using comparison operators
    • Implementing control flow
    • Handling panics
  3. Overview of Rust concurrency features

    main

    This chapter introduces Rust's 'fearless concurrency' model by transitioning single-threaded applications to multithreaded ones. You will work with the following core concurrency primitives and concepts:

    • Threads: Managed via the std::thread module.
    • Message passing: Implemented using channels.
    • Shared state: Managed using Arc, Mutex, and RwLock.
    • Concurrency guarantees: Encoded through the Send and Sync traits.
  4. Understand Heap vs Stack memory in Rust

    main

    In Rust, data whose size is not known at compile time (like collections and strings) is stored on the heap.

    • Stack: Fast, used for data with a known size at compile time.
    • Heap: Used for dynamically-sized data. You request memory from an allocator, which returns a pointer to the reserved block.
    • Performance: Heap allocations are slower than stack allocations due to the bookkeeping required by the allocator.
    • De-allocation: Unlike the stack, heap memory is not automatically freed by the stack mechanism; you must be deliberate about freeing memory (though Rust's ownership system manages this for you).
  5. Relationship between `async fn` and futures

    main

    When you define an async fn, the Rust compiler automatically transforms the function body into a state machine that implements the Future trait.

    Each .await point in your code corresponds to a state in this generated state machine. This allows the future to pause its execution and return control to the runtime, resuming from the exact same state when polled again.

  6. Understand integer overflow and underflow in Rust

    main

    An integer overflow occurs when the result of an arithmetic operation exceeds the maximum value representable by the given integer type. An integer underflow occurs when the result is smaller than the minimum value of the type.

    Rust does not perform automatic integer promotion (e.g., automatically converting a u8 to a u16 to accommodate a larger result). Instead, you must choose between two behaviors:

    1. Reject the operation: Stop the program via a panic.
    2. Wrap around: Treat the integer values as a circle where reaching the maximum value causes the result to wrap back to the minimum value (e.g., u8::MAX + 1 becomes u8::MIN).
  7. Understand Rust Traits and common standard library traits

    main

    Traits in Rust function as interfaces, defining shared behavior for different types. You will encounter traits in common operations such as .into() conversions and operators like == or +.

    Key standard library traits covered in this section include:

    • Operator traits: e.g., Add, Sub, PartialEq for mathematical and comparison operations.
    • Conversion traits: From and Into for infallible type conversions.
    • Copying traits: Clone and Copy for duplicating values.
    • Dereferencing: Deref and the mechanism of deref coercion.
    • Size traits: Sized to mark types with a known size at compile time.
    • Cleanup traits: Drop for implementing custom cleanup logic when a value goes out of scope.
  8. Understand Rust packages and crates

    main

    In Rust, a package is defined by a Cargo.toml file (the manifest). A package can contain one or more crates (also known as targets).

    There are two primary types of crates:

    • Binary crates: Programs that compile into an executable file. They must contain a main function as the entry point.
    • Library crates: Collections of code (functions, types, etc.) that cannot be run directly but are imported by other packages as dependencies.
  9. Understand detached threads and outliving parents

    main

    In Rust, a thread launched via thread::spawn is considered 'detached' in the sense that it can outlive the thread that spawned it. If a parent thread finishes and exits, its child threads will continue to run until the overall process terminates.

    use std::thread;
    
    fn f() {
        thread::spawn(|| {
            thread::spawn(|| {
                loop {
                    thread::sleep(std::time::Duration::from_secs(1));
                    println!("Hello from the detached thread!");
                }
            });
        });
    }
  10. Understand Stack memory behavior in Rust

    main

    The stack is a LIFO (Last In, First Out) memory region. In Rust, data is allocated on the stack when its size is known at compile time (e.g., primitive integers like u32 or i64).

    Key characteristics:

    • Stack Frames: When a function is called, a new stack frame is pushed onto the stack containing arguments, local variables, and bookkeeping values. When the function returns, the frame is popped.
    • Performance: Allocation and de-allocation are very fast because they only involve pushing/popping from the top of a contiguous block.
    • Stack Overflow: If function calls are nested too deeply, the program may run out of stack space, resulting in a stack overflow.