Introduction: Why Rust is a Challenge for Interviewers
Rust is a language that requires a deep understanding of memory management, the type system, and the concurrency model from a developer. A Rust interview is not a syntax check, but a test of understanding the language's philosophy. In this article, we will break down the key questions you may be asked in an interview — from the basics of ownership to advanced topics like async. You will learn what answers the interviewer expects and how to avoid common mistakes.
1. Ownership, Borrowing, and Lifetimes
1.1. What is ownership and how does it work?
This is the foundation of Rust. Each value has exactly one owner. When the owner goes out of scope, the value is dropped. This avoids garbage collection and guarantees memory safety at compile time.
fn main() { let s1 = String::from("hello"); let s2 = s1; // s1 is moved to s2 // println!("{}", s1); // Error! s1 is no longer valid println!("{}", s2); // Works}Tip: Explain the difference between move and copy. For types that implement the Copy trait (e.g., integers), a move does not occur — the data is copied.
1.2. Borrowing rules
Borrowing allows you to use a reference to data without transferring ownership. There are two types of references:
- Immutable (&T): there can be as many as you want, the data cannot be changed.
- Mutable (&mut T): only one reference to the data in the current scope.
fn main() { let mut s = String::from("hello"); let r1 = &s; // immutable reference let r2 = &s; // another immutable reference — ok // let r3 = &mut s; // Error! Cannot have both immutable and mutable references simultaneously println!("{} {}", r1, r2);}Trick question: "Why does Rust forbid having both mutable and immutable references at the same time?" Answer: to prevent data races at compile time.
1.3. Lifetimes — how to explain them simply
Lifetimes are annotations that tell the compiler how long references will be valid. Most often, the compiler infers them, but in complex functions, you have to specify them explicitly.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}Key idea: 'a means that the returned reference will live no longer than both input references. This guarantees that the reference will not become dangling.
2. Enums, Pattern Matching, and Traits
2.1. Enum in Rust — why it is more powerful than in other languages
In Rust, enums can store data of any type — strings, numbers, structs, and even other enums. This allows you to create Algebraic Data Types (ADTs).
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32),}Typical question: "How does Option<T> work and why doesn't Rust have null?" Answer: Option is an enum that can be Some(T) or None. The compiler forces you to handle both variants, which eliminates null pointer errors.
2.2. Pattern matching
The match construct is the heart of Rust. It must be exhaustive.
fn describe_point(point: (i32, i32)) -> &'static str { match point { (0, 0) => "origin", (0, _) => "on the X axis", (_, 0) => "on the Y axis", _ => "somewhere in the plane", }}Interview hack: Use if let for