Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
142: Common Rust Interview Questions on Lifetimes
When you get to the "Lifetimes" portion of a Rust interview, the interviewer isn't usually trying to see if you can memorize the syntax. They want to see if you understand how the borrow checker thinks. Most candidates can write 'a when the compiler screams at them, but the real test is explaining why it's necessary.
Let's start with a piece of code that looks perfectly logical to a C++ or Java developer, but makes the Rust compiler throw a fit. This is a classic "longest string" utility that I've seen many juniors struggle with in technical screens.
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
The "Which one is it?" Compiler Error
If you try to compile this, Rust will tell you that the missing lifetime specifier is a problem. You might think, "Why? Both inputs are string slices, and I'm returning a string slice. It's obvious!"
But here is the catch: the compiler doesn't look at the if/else logic to determine the lifetime. It looks at the function signature. From the compiler's perspective, the return value is a reference, but it doesn't know if that reference is tied to x or y. If x lived for 10 seconds and y lived for 5 seconds, the compiler needs to know the minimum guaranteed lifetime of the result so it can prevent you from using that result after y has been dropped.
Linking Input and Output with Generic Lifetimes
To fix this, we use a generic lifetime parameter. I like to think of this as a "contract" we're signing with the compiler.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
By adding &'a to both inputs and the output, we aren't changing how long the variables live. Instead, we're telling Rust: "The returned reference will live at least as long as the shorter of the two inputs." If you pass in one reference that lasts for the whole program and one that lasts for a single function call, the return value is treated as if it only lasts for that single function call. It's the "lowest common denominator" approach.
Explaining Lifetime Elision to an Interviewer
A common follow-up question in interviews is: "If lifetimes are required, why don't I have to write them for every single function?"
The answer is Lifetime Elision. Rust has a set of rules that allow the compiler to guess (elide) the lifetimes in common patterns so we don't have to clutter our code. For example, if there is exactly one input reference, that lifetime is automatically assigned to all output references. This is why a simple fn first_char(s: &str) -> &str works without any 'a markers.
In an interview, I recommend explaining it like this: "Lifetime elision is just syntactic sugar. The compiler is applying a set of predefined rules to fill in the blanks. When those rules aren't enough—like when we have multiple input references and an ambiguous output—we have to step in and be explicit."
The 'static Lifetime Trap
You'll also likely be asked about 'static. Beginners often think 'static means "this variable lives forever," but it actually means "this reference is guaranteed to be valid for the entire duration of the program."
String literals (like "Hello") are 'static because they are embedded directly into the program's binary. If an interviewer asks you to implement a function that returns a hard-coded configuration string, you can either let elision handle it or explicitly mark it as &'static str to show you know exactly what's happening under the hood.
📋 Practical Task
Fixing the TextParser Struct Lifetimes
You are building a simple TextParser that holds a reference to a source string and a reference to the "current" word being processed. Currently, the code won't compile because the struct doesn't specify how long the references inside it should last.
Your Task: Modify the TextParser struct and its new and next_word methods to include the necessary lifetime annotations so that the borrow checker knows the parser cannot outlive the source text it is parsing.
struct TextParser {
source: &str,
current_word: &str,
}
impl TextParser {
fn new(text: &str) -> Self {
TextParser {
source: text,
current_word: "",
}
}
fn next_word(&mut self) -> &str {
// Simplified logic: just returns the source for this exercise
self.current_word = self.source;
self.current_word
}
}
fn main() {
let text = String::from("Rust is awesome");
let mut parser = TextParser::new(&text);
println!("Word: {}", parser.next_word());
}There are no comments for now.