The questions that come up most often, each with a sample answer you can adapt into your own words. Read them out loud until the explanation feels natural.
What are the rules of ownership in Rust?
IntermediateEach value has exactly one owner; there can be only one owner at a time; and when the owner goes out of scope, the value is dropped (its memory freed). Assigning or passing a non-Copy value moves ownership, after which the original binding can no longer be used. These rules let Rust free memory deterministically without a garbage collector and prevent double-frees.
Explain the borrowing rules.
IntermediateYou can have either one mutable reference (&mut T) or any number of immutable references (&T) to a value at the same time, but never both simultaneously. References must also never outlive the data they point to. Together these rules guarantee at compile time that you cannot have a data race or a dangling reference, which is the core of Rust's safety.
What is a lifetime and why do annotations exist?
AdvancedA lifetime is the scope for which a reference is valid. The compiler usually infers lifetimes, but when a function returns a reference derived from its inputs, you sometimes annotate (like fn longest<'a>(x: &'a str, y: &'a str) -> &'a str) to tell the compiler how the output's validity relates to the inputs. Lifetimes do not change how long data lives; they are a contract the borrow checker verifies so no reference outlives its referent.
How does Rust handle null and errors?
IntermediateRust has no null. Absence is modelled with Option<T> (Some or None), forcing you to handle the empty case explicitly. Recoverable errors use Result<T, E> (Ok or Err), and the ? operator propagates an Err upward early, keeping the happy path clean. Unrecoverable errors use panic!. This pushes error handling into the type system so the compiler ensures you address it.
What is the difference between Box, Rc, and Arc?
AdvancedBox<T> is a single-owner heap allocation, used for recursive types or to move a large value to the heap. Rc<T> is a reference-counted shared owner for single-threaded use, allowing multiple owners of the same data. Arc<T> is the atomically reference-counted version that is safe to share across threads, at the cost of atomic operations. You combine Arc with a Mutex when you need shared mutable state across threads.
What is a trait?
IntermediateA trait defines shared behaviour as a set of method signatures that types can implement, similar to an interface. Traits enable generics through trait bounds (fn f<T: Display>(x: T)) for compile-time static dispatch, and trait objects (dyn Trait) for runtime dynamic dispatch. Standard traits like Clone, Iterator, and Drop hook types into the language's machinery.
What does it mean that a type is Send and Sync?
AdvancedSend means a type can be safely transferred to another thread; Sync means it is safe to share a reference to it across threads. Most types are automatically Send and Sync; types like Rc are not Send because their non-atomic counter would race. The compiler uses these marker traits to make passing unsafe data between threads a compile error, which is how Rust delivers fearless concurrency.
What is interior mutability and when is it needed?
AdvancedInterior mutability lets you mutate data through an immutable reference, with the borrow rules checked at runtime instead of compile time. Cell and RefCell provide it for single-threaded code (RefCell panics if you violate the borrow rules at runtime), and Mutex/RwLock provide it across threads. You reach for it when the ownership model is too rigid for a legitimate pattern, such as a shared cache, while still upholding the one-writer rule.
What is the difference between String and &str?
FundamentalsString is an owned, growable, heap-allocated string; &str is a borrowed view into string data, whether that data lives in a String or in the binary as a literal. Functions should usually accept &str, since a &String coerces to &str automatically and the function then works with both. You allocate a String when you need to build or mutate text, and hand out &str slices when callers only need to read. Muddling the two is the first borrow-checker fight most newcomers have, so a clean explanation lands well.
What does the ? operator do?
Intermediate? unwraps a Result or Option: on Ok or Some it yields the inner value and execution continues; on Err or None it returns early from the enclosing function, converting the error type via From where needed. It collapses the match-and-return boilerplate that errors-as-values would otherwise create, keeping the happy path linear while every failure point stays visible as a single character. It only works in functions whose return type is compatible, which is why application code so often returns Result<T, Box<dyn Error>> or a crate-specific error type.
use std::fs;
fn read_port(path: &str) -> Result<u16, Box<dyn std::error::Error>> {
let text = fs::read_to_string(path)?;
let port: u16 = text.trim().parse()?;
Ok(port)
}
How do iterators work in Rust?
IntermediateAnything implementing the Iterator trait yields items via next(), and adapter methods like map, filter, and take build lazy pipelines that do no work until a consumer such as collect, sum, or a for loop drives them. Iterators are zero-cost: the chains compile down to code as fast as a hand-written loop. Idiomatic Rust transforms collections through iterator chains rather than indexing, and collect's ability to gather into different containers guided by type inference, including collecting into a Result that short-circuits on the first error, is a favourite senior probe.
let scores = vec![78, 92, 61, 88];
let passed: Vec<i32> = scores
.iter()
.filter(|&&s| s >= 70)
.map(|&s| s + 5)
.collect();
What is the difference between Copy and Clone?
AdvancedClone is an explicit, potentially expensive duplication: calling .clone() may allocate, as it does for String or Vec. Copy is a marker trait for types whose duplication is a cheap bitwise copy (integers, floats, bool, shared references); for these, assignment copies instead of moving, so the original stays usable. A type can only be Copy if all its fields are, and it cannot implement Drop. The practical consequence: passing an i32 around never triggers ownership errors, while doing the same with a String moves it, and explaining why shows you genuinely understand moves.