Rust Interview Questions
Real Rust interview questions and answers on ownership, borrowing, Option, Result, and traits.
A curated set of Rust interview questions, ordered roughly from fundamentals to more advanced — the kind you'll actually be asked in real screens and on-sites.
Ownership and borrowing
Q: What are the three rules of ownership in Rust? Each value has exactly one owner at a time; there can only be one owner at any given moment; and when the owner goes out of scope, the value is automatically dropped (its memory freed). Together these rules guarantee memory is freed exactly once, with no garbage collector required.
Q: What's the difference between moving, borrowing, and cloning a value?
Moving transfers ownership to a new variable and invalidates the original binding — no copy happens. Borrowing (& or &mut) lets code temporarily use a value through a reference without taking ownership of it, so the original owner remains valid. Cloning (.clone()) explicitly creates an independent deep copy of the data, so both the original and the copy remain valid and own their own memory.
Q: Can you have a mutable reference and an immutable reference to the same value at the same time? No. The borrow checker enforces that at any point, a value can have either any number of immutable references or exactly one mutable reference, never both simultaneously. This rule is what makes data races impossible to compile in safe Rust — a data race requires concurrent access where at least one access is a write, and this rule rules that out structurally.
Type system and error handling
Q: How does Option<T> avoid the "billion-dollar mistake" of null references?
Option<T> makes absence part of the type system: a value is either Some(T) (a real value) or None, and these are a distinct type from T itself. The compiler forces you to explicitly handle both cases (via match, if let, or combinators like .unwrap_or()) before you can extract the inner value, so there's no way to accidentally treat an absent value as if it were present.
Q: Why does Rust prefer Result<T, E> over exceptions for recoverable errors?
Because the possibility of failure is encoded directly in a function's return type, visible at every call site, rather than being an invisible, silently-propagating side channel like a thrown exception. The ? operator keeps propagation concise without hiding it — you can tell exactly which functions can fail just by reading their signatures, and the compiler won't let you ignore an Err without at least acknowledging it.
Q: What's the practical difference between a trait object (dyn Trait) and a generic constrained by a trait bound (impl Trait / <T: Trait>)?
A generic is resolved at compile time — the compiler generates a specialized copy of the function for each concrete type used (monomorphization), enabling inlining and typically better performance, at the cost of larger binaries. A trait object is resolved at runtime through a vtable (dynamic dispatch) — one shared function body handles every concrete type, which is necessary when you need a single collection or variable to hold several different concrete types behind a shared interface, but each call has a small indirection cost.
Language design
Q: Why doesn't Rust have a garbage collector? Because ownership and borrowing let the compiler prove, statically, exactly when a value's memory can be freed — there's no need to track reference counts or scan the heap at runtime. This gives Rust predictable, GC-pause-free performance suitable for systems programming, game engines, and other latency-sensitive workloads, while still preventing use-after-free and double-free bugs.
Error handling and concurrency in practice
Q: What's the difference between thiserror and anyhow, and when would you reach for each?
thiserror is a derive macro that generates the Display/Error/From boilerplate for a custom error enum, intended for library code whose callers need to match on specific failure variants. anyhow provides one flexible anyhow::Error type that any error can convert into via ?, plus easy context attachment, intended for application (binary) code that just needs to propagate and report errors without exposing a typed enum for callers to match on.
Q: How do Arc<Mutex<T>> and mpsc channels differ as strategies for sharing data across threads?
Arc<Mutex<T>> lets multiple threads share and mutate one value, one at a time, with the compiler enforcing that no code can touch the data without first acquiring the lock via .lock(). mpsc channels instead have threads send owned values to each other — sending a value moves it, so the sender and receiver never hold it simultaneously. Shared state suits a small, frequently-touched value like a counter; channels suit a pipeline or worker-pool pattern collecting independent results.
Q: What's the difference between a unit test and an integration test in a Cargo project?
A unit test lives inside src/, typically in a #[cfg(test)] mod tests block, and can access private items in the same module. An integration test lives in a separate file under a top-level tests/ directory, is compiled as its own independent crate, and can only reach the crate's public API — exactly what an external consumer of the crate would be limited to.