askesis

Rust flashcards: 50 essentials with code

Learn the Rust that the compiler keeps asking about: ownership, borrowing, lifetimes, Option and Result, traits, closures, smart pointers and async.

ProgrammingIntermediate50 cardsFree

Practice now

Study without an account

0 of 50 seen0 graduated
The three ownership rules

Scheduled with the default Askesis timings. Progress is saved in this browser.

All 50 cards

  1. 1
    The three ownership rules
    Each value has one owner; there is only one owner at a time; when the owner goes out of scope, the value is dropped
  2. 2
    What happens to a after let b = a; when a is a String?
    The value moves to b; using a afterwards is a compile error
    error[E0382]: borrow of moved value: a
  3. 3
    Which types are copied instead of moved?
    Types that implement Copy: integers, floats, bool, char, shared references, and tuples/arrays of Copy types
  4. 4
    Copy vs Clone
    Copy is an implicit bitwise copy for cheap types; Clone is an explicit, possibly expensive .clone()
    Every Copy type must also be Clone.
  5. 5
    The borrowing rule
    At any time you can have either one &mut T or any number of &T, and references must not outlive the value
  6. 6
    Why doesn’t this compile?
    You can’t borrow v mutably (push) while an immutable borrow (first) is still in use
    rust
    let mut v = vec![1, 2, 3];
    let first = &v[0];
    v.push(4);
    println!("{first}");
  7. 7
    String vs &str
    String is an owned, growable, heap-allocated buffer; &str is a borrowed view of UTF-8 text
  8. 8
    Best parameter type for read-only text
    &str
    A &String coerces to &str, so the function accepts both.
  9. 9
    Why can’t you index a String with s[0]?
    Strings are UTF-8, so a byte index may not be a character boundary
    Use s.chars().nth(0) or slice on a known boundary like &s[0..1].
  10. 10
    What does this signature say?
    The returned reference lives as long as the shorter of x and y
    rust
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
  11. 11
    What does 'static mean on a reference?
    The data lives for the whole program, like string literals
  12. 12
    Lifetime elision with one reference input
    The output reference gets that input’s lifetime, so you don’t write it
  13. 13
    What is Option<T>?
    Some(T) or None: Rust’s way to model an absent value instead of null
  14. 14
    What is Result<T, E>?
    Ok(T) or Err(E): the return type for operations that can fail
  15. 15
    What does the ? operator do?
    Unwraps Ok/Some, or returns the Err/None from the current function early
    rust
    fn read_config() -> Result<String, std::io::Error> {
        let text = std::fs::read_to_string("config.toml")?;
        Ok(text)
    }
  16. 16
    How does ? convert error types?
    It calls From::from on the error, so it works when the function’s error type implements From<SourceError>
  17. 17
    unwrap() vs expect("msg")
    Both panic on None/Err; expect adds your message to the panic
  18. 18
    unwrap_or vs unwrap_or_else
    unwrap_or(v) evaluates v eagerly; unwrap_or_else(|| v) only computes it when needed
  19. 19
    Turn an Option into a Result
    rust
    let user = find_user(id).ok_or(Error::NotFound)?;
  20. 20
    Are match expressions required to be exhaustive?
    Yes. Every possible value must be covered, often with a _ catch-all
  21. 21
    Match one variant and ignore the rest
    rust
    if let Some(user) = maybe_user {
        greet(&user);
    }
  22. 22
    let ... else
    Destructure or bail out with a diverging block
    rust
    let Some(port) = args.port else {
        return Err("missing port".into());
    };
  23. 23
    Match guard
    An extra if condition on a match arm
    rust
    match n {
        x if x < 0 => "negative",
        0 => "zero",
        _ => "positive",
    }
  24. 24
    What is a trait?
    A set of methods a type can implement, like an interface; traits can provide default methods
  25. 25
    Generic function with a trait bound
    rust
    fn largest<T: PartialOrd + Copy>(items: &[T]) -> T
  26. 26
    Same bound written with where
    rust
    fn largest<T>(items: &[T]) -> T
    where
        T: PartialOrd + Copy,
  27. 27
    impl Trait in argument position
    Shorthand for a generic parameter: fn show(item: impl Display) accepts any Display type
  28. 28
    impl Trait in return position
    Returns one concrete type that the caller only knows by its trait, e.g. -> impl Iterator<Item = u32>
  29. 29
    Generics vs dyn Trait
    Generics are monomorphized (static dispatch, one copy per type); dyn Trait uses a vtable at runtime (dynamic dispatch)
  30. 30
    Store different types that share a trait in one Vec
    rust
    let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Circle(1.0)), Box::new(Square(2.0))];
  31. 31
    The orphan rule
    You can implement a trait for a type only if your crate defines the trait or the type
  32. 32
    Which traits can #[derive] give you?
    Standard ones like Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default
  33. 33
    Fn vs FnMut vs FnOnce
    Fn borrows its captures, FnMut mutably borrows them, FnOnce may consume them and can be called once
  34. 34
    What does move do on a closure?
    Forces the closure to take ownership of the variables it captures
    rust
    let name = String::from("ada");
    std::thread::spawn(move || println!("{name}"));
  35. 35
    Are iterator adapters like map and filter eager?
    No, they’re lazy; nothing runs until a consumer like collect, sum or a for loop drives them
  36. 36
    Sum the squares of the even numbers
    rust
    let total: i32 = nums.iter().filter(|&&n| n % 2 == 0).map(|&n| n * n).sum();
  37. 37
    iter() vs iter_mut() vs into_iter()
    Yields &T, &mut T, or owned T (consuming the collection)
  38. 38
    Collect an iterator of Results, stopping at the first error
    rust
    let nums: Result<Vec<i32>, _> = input.lines().map(str::parse::<i32>).collect();
  39. 39
    When do you need Box<T>?
    To put a value on the heap: recursive types, large values, or trait objects (Box<dyn Trait>)
  40. 40
    Rc<T> vs Arc<T>
    Both are reference-counted shared ownership; Arc uses atomic counts and is safe to share across threads
  41. 41
    What is RefCell<T>?
    Interior mutability with borrow rules checked at runtime; a violation panics
    Common pattern for shared mutable data in one thread: Rc<RefCell<T>>.
  42. 42
    Shared mutable state across threads
    rust
    let counter = Arc::new(Mutex::new(0));
    *counter.lock().unwrap() += 1;
  43. 43
    Send vs Sync
    Send: ownership can move to another thread. Sync: &T can be shared between threads
  44. 44
    Why isn’t Rc<T> Send?
    Its reference count isn’t atomic, so two threads could corrupt it; use Arc<T>
  45. 45
    Library vs application error handling
    Libraries usually define error enums (often with thiserror); applications often use anyhow::Result with context
  46. 46
    What does unsafe let you do?
    Dereference raw pointers, call unsafe functions (including FFI), access mutable statics, implement unsafe traits, access union fields
  47. 47
    Does unsafe turn off the borrow checker?
    No. It only allows those extra operations; you take responsibility for upholding the safety rules
  48. 48
    What does an async fn return?
    A Future; nothing runs until it is .awaited or polled by an executor like Tokio
  49. 49
    Make an item public and add a dependency
    Mark items pub to export them; add crates with cargo add <crate> (written to Cargo.toml)
  50. 50
    cargo check vs cargo build
    check type-checks without producing a binary, so it’s much faster during development

Frequently asked questions

Is this deck for Rust beginners?

It suits anyone who has started the Rust book or written a little Rust. It focuses on the concepts that cause the most compiler errors.

Which Rust edition does it use?

Everything applies to the 2021 and 2024 editions of stable Rust.

Does it cover async Rust?

It covers the basics (futures and .await). Executors such as Tokio are only mentioned.

Intermediate

JavaScript

Master the JavaScript every developer uses daily: scope, closures, equality, array methods, promises, async/await and the event loop.

50 cards
Intermediate

Advanced JS

Fifty advanced JavaScript cards: event-loop ordering puzzles, generators and async iteration, this binding, Proxy and Reflect, modules, AbortController and coercion edge cases.

50 cards
Intermediate

TypeScript

Learn the TypeScript you use every day: narrowing, generics, keyof, mapped and conditional types, utility types, satisfies and strict mode, with code on every card.

50 cards
Beginner

Python

Learn idiomatic Python fast: comprehensions, f-strings, args and kwargs, generators, decorators, context managers and the standard library.

50 cards
Beginner

Git & terminal

The Git commands and shell skills developers use every day: branching, rebasing, undoing mistakes, pipes, redirects, permissions and processes.

50 cards
Beginner

Java

Core Java with short snippets: equals vs ==, records, sealed types, collections, generics, exceptions, streams, lambdas and JVM basics.

50 cards
Beginner

C#

C# and .NET fundamentals with short snippets: value vs reference types, properties, records, nullable reference types, LINQ, async/await and DI.

50 cards
Beginner

Kotlin

Kotlin fundamentals with short snippets: null safety, val vs var, data and sealed classes, extension functions, scope functions and coroutines.

50 cards
Beginner

Swift

Swift fundamentals with short snippets: optionals, structs vs classes, protocols, enums with associated values, closures, ARC, async/await and SwiftUI.

50 cards
Beginner

Flutter

Flutter and Dart basics: null safety, futures and streams, stateless vs stateful widgets, setState, hot reload, keys, layout and state management.

50 cards
Beginner

PHP

Modern PHP 8 with short snippets: strict types, arrays, match, nullsafe and named arguments, enums, readonly, Composer, PDO and password_hash.

50 cards
Beginner

Ruby

Ruby fundamentals with short snippets: blocks, procs and lambdas, symbols, Enumerable, mixins, truthiness, method_missing, gems and Bundler.

50 cards
Beginner

Go

Go (Golang) fundamentals with short, idiomatic snippets: slices vs arrays, maps, defer, error wrapping, interfaces, goroutines, channels, select and context.

50 cards
Intermediate

C++

Modern C++ in short snippets: references vs pointers, RAII, smart pointers, move semantics, STL containers and algorithms, const, templates and UB.

50 cards
Intermediate

Haskell

Haskell fundamentals with short code: types and type classes, pattern matching, laziness, Maybe and Either, folds, Functor, Applicative and Monad.

50 cards
Intermediate

Elixir

Elixir fundamentals with short snippets: immutability, pattern matching, the pipe operator, Enum vs Stream, processes, GenServer, supervisors and mix.

50 cards
Intermediate

x86-64 assembly

x86-64 assembly in Intel syntax: registers and sub-registers, flags, mov and lea, jumps, the stack frame, the System V calling convention and Linux syscalls.

50 cards
Beginner

Regex

Learn regular expressions for good: anchors, classes, greedy vs lazy quantifiers, groups, lookarounds, flags, replacements and the backtracking trap.

50 cards