112 companion flashcards · AI-assisted study content · Open the deck →
This deck covers the foundational concepts of Rust, a systems programming language known for its focus on memory safety, performance, and modern tooling. The cards walk through core ideas like ownership, borrowing, and lifetimes — the features that make Rust unique — as well as essential building blocks such as structs, enums, pattern matching, and the Option and Result types used for handling values and errors. Together, these topics form the backbone of writing idiomatic, safe Rust code.
It's a great fit if you're just starting out with Rust and want a structured way to review the basics, or if you're an experienced developer from another language who needs to get comfortable with how Rust thinks about memory and data. The questions are framed in an interview-style format, making it useful for anyone preparing for technical interviews or quizzes as well.
Because Rust's ownership and borrowing rules are very different from what most languages use, it's worth taking your time with each card and trying to explain the concept in your own words before moving on. Spacing your review across several short sessions tends to work better than cramming, especially for the trickier topics like lifetimes. Pair the flashcards with small coding experiments — even a few lines in the Rust playground — and the concepts will stick much more reliably.
Rust is a systems programming language that targets three goals at once: safety, speed, and concurrency. What distinguishes it from most peers is that it achieves memory safety without a garbage collector, instead relying on a compile-time ownership system that the borrow checker enforces on every program.
The ownership model rests on three rules. Every value in Rust has exactly one owner, there can only be one owner at a time, and when that owner goes out of scope the value is dropped. Because these rules are checked statically, entire classes of bugs — use-after-free, double-free, and many memory leaks — become impossible rather than merely unlikely.
When code needs to access a value without taking ownership of it, it borrows the value through a reference. An immutable reference, written as `&T`, is a shared borrow: many immutable references may coexist freely. A mutable reference, written as `&mut T`, is an exclusive borrow: at most one such reference may exist, and no immutable references may exist alongside it. The compiler verifies these aliasing rules on every function, which means the same checks that prevent data races in single-threaded code also prevent them across threads.
Lifetimes are the annotations that describe how long references remain valid. A lifetime parameter such as `'a` ties a reference's validity to a particular scope, and the compiler uses these annotations to prevent dangling references. The `'static` lifetime is a special case that means the reference is valid for the entire duration of the program, which is precisely why string literals have the type `&'static str`. Together, ownership, borrowing, and lifetimes form the foundation that makes Rust's memory safety guarantees possible.
Rust offers two principal ways to compose custom data types: structs and enums. A struct groups related fields together, and Rust supports three flavors — named-field structs, tuple structs, and unit structs — so you can pick the form that best fits your data. Behavior is attached separately using an `impl` block, where methods take `&self`, `&mut self`, or `self` as their first parameter to declare how they interact with the receiver.
Enums declare a type whose values are one of several named variants, and each variant can carry its own data. A shape might be either a `Circle(f64)` carrying a radius or a `Rectangle { width: f64, height: f64 }` carrying its dimensions. This is a true sum type: every value encodes both which case it belongs to and the data for that case, which makes enums far more expressive than their counterparts in most other languages.
Pattern matching with `match` is the natural companion to these types. A `match` expression destructures a value and branches on its shape, while the compiler enforces exhaustiveness: every variant must be handled, or the program will not compile. This guarantee eliminates the entire class of bugs where a developer forgets to consider one branch, and it encourages designers to make the state space of a type fully explicit.
For situations where a full `match` would be overkill, Rust offers `if let` to handle a single interesting pattern and a `while let` loop that continues as long as a pattern keeps matching. Both are syntactic sugar for a `match` with one meaningful arm and a wildcard, and they let you write tighter code when the alternatives are not interesting enough to enumerate.
Rust replaces the concept of `null` with the `Option
There are several idiomatic ways to work with an `Option`. Calling `unwrap()` returns the inner value or panics if it is `None`, which is appropriate only when you are certain the value exists. `unwrap_or(default)` provides a fallback instead of panicking. `if let Some(v) = opt { ... }` and `match` give you explicit control over both branches. The `?` operator applied to an `Option` returns the inner value when it is `Some` and early-returns `None` to the caller otherwise, which keeps chains of fallible optional operations short and readable.
For operations that can fail in a recoverable way, Rust uses `Result
The distinction between `panic!` and `Result` is intentional. A `panic!` is for unrecoverable errors: it unwinds the stack and typically aborts the current thread, which is appropriate for invariant violations and bugs that the program cannot safely continue past. `Result` is for errors the caller should be able to handle, such as a missing file or malformed input. Library code almost always prefers `Result`, while `panic!` is reserved for situations where continuing would corrupt the program's state.
Traits are Rust's mechanism for describing shared behavior. A trait declares a set of methods that a type must implement, much like an interface in object-oriented languages, and a type implements a trait with `impl Trait for Type { ... }`. Once a trait is implemented, the type can be used anywhere the trait is required, which makes traits the foundation of Rust's polymorphism.
Generic functions and types use type parameters so that the same code can operate on many concrete types. To guarantee that a generic parameter supports the operations the function needs, you add trait bounds — either inline with `T: Trait` or with a `where` clause. At compile time, Rust performs monomorphization, generating a specialized copy of the code for each concrete type that is actually used, so generic code carries zero runtime cost compared to hand-written per-type versions.
Many useful behaviors are expressed as traits that the compiler can implement for you automatically when you opt in with `#[derive(...)]`. `Debug` enables `{:?}` formatting for inspection; `Clone` provides an explicit `.clone()`; `Copy` allows implicit bitwise duplication for stack-only types like integers and booleans; `PartialEq` and `Eq` enable equality; `PartialOrd` and `Ord` enable ordering; and `Hash` enables use in hash-based collections. All `Copy` types must also implement `Clone`, but not vice versa.
Two additional traits round out the common vocabulary. `From
Iterators are the foundation of Rust's approach to sequences. Any type that implements the `Iterator` trait must provide a `next()` method returning `Option
Iterator methods come in two flavors. Adapters such as `map()`, `filter()`, and `take()` return new iterators without doing any work themselves, so they can be chained freely. Consumers such as `collect()`, `sum()`, `count()`, and `for_each()` actually drive the iteration and produce a final value or side effect. A typical pipeline filters a range to keep only even numbers, squares each remaining element with `map`, and gathers the results into a `Vec
Closures are anonymous functions that can capture variables from their enclosing scope. They appear constantly in iterator pipelines, where they serve as the arguments to `map`, `filter`, and similar adapters. The compiler classifies every closure's capture behavior with one of three traits: `Fn` for closures that capture by immutable reference, `FnMut` for those that capture by mutable reference, and `FnOnce` for those that consume their captured variables and can therefore be called only once. Every closure implements at least `FnOnce`, and many also implement `FnMut` or `Fn`.
The `move` keyword forces a closure to take ownership of everything it captures rather than borrowing. This is essential when a closure will outlive the scope in which it was created — for example, when it is sent to another thread — because the captured values must continue to live independently. Closures, iterators, and adapters together form a powerful functional style that compiles down to efficient imperative code.
Rust organizes code using modules, declared with the `mod` keyword. A module is a namespace that can hold functions, structs, enums, constants, and other modules. Items inside a module are private by default and become visible to outside code only when marked `pub`. This explicit visibility model lets library authors expose a precise public API while keeping implementation details hidden from consumers.
Modules can be defined inline or placed in separate files. The declaration `mod foo;` instructs the compiler to look for either `foo.rs` alongside the parent or a `foo/mod.rs` directory. Since Rust 2018, the convention of using `foo.rs` with a sibling `foo/` directory for submodules has become preferred because it avoids the clutter of `mod.rs` and reads more naturally.
Cargo is Rust's build system and package manager. Common commands include `cargo new` to scaffold a project, `cargo build` to compile, `cargo run` to build and execute, and `cargo test` to run the test suite. Dependencies are declared in `Cargo.toml`, and external libraries are downloaded from crates.io, the central registry of the Rust ecosystem.
A crate is the unit of compilation in Rust. It is either a binary crate, which contains a `main` function and produces an executable, or a library crate, which exposes functionality for other crates to use. Tests are typically written in a `#[cfg(test)]` module within the same file as the code they exercise, using `#[test]` attributes on individual functions and macros such as `assert_eq!` and `assert!` to express expectations. Running `cargo test` compiles and executes them all.
Rust's concurrency story begins with `std::thread::spawn`, which takes a closure and returns a `JoinHandle`. Calling `.join().unwrap()` on the handle blocks the current thread until the spawned thread has finished. Closures sent to threads almost always need the `move` keyword so that they own the data they operate on rather than borrowing it from the spawning thread, which would otherwise cause lifetime problems once the spawning thread moves on.
Two marker traits underpin safe concurrency. `Send` indicates that a value of a type can be transferred to another thread, while `Sync` indicates that a value can be referenced from multiple threads — formally, `&T` is `Send` whenever `T` is `Sync`. Most standard types implement both automatically. The single-threaded reference-counted pointer `Rc
For shared state across threads, the standard library offers `Arc
When shared state is awkward, channels provide an alternative based on message passing. The `std::sync::mpsc` module — short for multiple producer, single consumer — gives you a sender `tx` and a receiver `rx`. The sender hands values to the receiver, which can collect them. Channels naturally serialize access through communication rather than through shared locks, and they are often the most ergonomic way to coordinate work between threads.
Beyond plain references, Rust offers smart pointers that own or manage data in more sophisticated ways. `Box
For shared ownership within a single thread, `Rc
For the rare situations where the compiler's safety guarantees are too restrictive, Rust provides the `unsafe` keyword. Inside an `unsafe` block you may dereference raw pointers, call unsafe functions, access or modify mutable statics, implement unsafe traits, and access union fields. Importantly, `unsafe` does not disable the borrow checker; it merely opens a small set of additional capabilities that the programmer must verify by hand and document carefully.
Raw pointers, written `*const T` and `*mut T`, are at the heart of unsafe code. They resemble C pointers: they can be null, dangling, or aliased without complaint from the compiler. Creating a raw pointer is safe, but dereferencing one requires `unsafe`. Used sparingly, with clear invariants and thorough documentation, unsafe code lets Rust interoperate with hardware, foreign functions, and performance-critical low-level patterns while keeping the vast majority of the codebase in safe Rust.
Option<T> represents a value that may or may not exist:enum Option<T> { Some(T), None }mod foo; tells the compiler to look for foo.rs or foo/mod.rs. Since Rust 2018, foo.rs with a sibling directory foo/ is preferred over foo/mod.rs.std::sync::mpsc (multiple producer, single consumer):let (tx, rx) = mpsc::channel();
tx.send(42).unwrap();
let val = rx.recv().unwrap();From<T> defines how to create a type from another:impl From<&str> for StringInto<T> is the reciprocal — implementing From automatically provides Into. Widely used for ergonomic type conversions and error handling.i32, u64), floating-point (f32, f64), boolean (bool), and character (char). They represent a single value.unwrap_or_else(closure) returns the inner value or, on None/Err, lazily evaluates the closure to produce the fallback. It avoids computing the default unless needed, unlike unwrap_or.TryInto is a fallible conversion that returns a Result, used when a conversion can fail (e.g., u16::try_from(big)). It is the counterpart of Into for conversions that may not succeed.dyn) uses dynamic dispatch (runtime vtable, single type, slight overhead).HashMap stores keys in an arbitrary order with O(1) average lookup. BTreeMap keeps keys sorted and supports range queries, with O(log n) operations. Choose BTreeMap when iteration order matters.Drill this topic
112 flashcards on Rust Programming — free, no signup needed to start.
Study Rust Programming flashcardsLearnWiki pages are generated with AI assistance from LearnCoachAssist's reviewed study catalog and may contain errors — verify anything critical against your course materials.