Interview preparation guide

Rust Interview Questions: Borrow Errors, Option and Result

These Rust interview questions follow two small examples: a vector edit that fails borrow checking and an optional numeric field parser. You will explain a compiler error, make a minimal repair and test valid, missing and malformed inputs. The examples use only the standard library.

Start with a compile failure you can explain

The exercise uses Rust 1.90.0 with edition 2024 on macOS arm64. Save this intentionally invalid program as borrow_bad.rs. Compile it with rustc --edition=2024 borrow_bad.rs; a successful build would be unexpected for this example.

fn main() {
    let mut counts = vec![4_u16, 7];
    let first = &mut counts[0];
    counts.push(9);
    *first += 1;
}

1. Why does the first program fail?

The mutable reference first refers to an element in counts, and it is used after counts.push(9). Push needs to mutably borrow the vector while the earlier mutable borrow is still needed. The compiler rejects that overlapping use. In the tested compiler, the diagnostic is E0499: counts cannot be borrowed as mutable more than once at a time.

Follow-up practice: Point to the creation of the first borrow, the conflicting operation and the later use. Explaining all three is more useful than saying “Rust does not allow mutation.”

2. Can you repair it without cloning the vector?

Yes. Complete the update through first before calling push, and do not use first afterward. In the corrected program below, the borrow’s last use is *first += 1. The subsequent push can then borrow counts. The owner remains the same vector; no clone or unsafe code is needed.

Follow-up practice: Would adding another use of first after push make the repair invalid again? Try that as a deliberate compile-failing variation and explain the lifetime change.

Run the corrected program and parser checks

Save this as record_lab.rs. The assertions verify the final vector and five parser boundaries. The parser returns a Result whose success value is an Option, preserving a distinction between an absent field and a present field that could not be parsed.

use std::num::ParseIntError;

fn optional_count(raw: Option<&str>) -> Result<Option<u16>, ParseIntError> {
    raw.map(str::parse::<u16>).transpose()
}

fn main() {
    let mut counts = vec![4_u16, 7];
    let first = &mut counts[0];
    *first += 1; // Last use of this mutable borrow.
    counts.push(9);
    assert_eq!(counts, vec![5, 7, 9]);
    assert_eq!(optional_count(Some("18")).unwrap(), Some(18));
    assert_eq!(optional_count(None).unwrap(), None);
    assert!(optional_count(Some("eighteen")).is_err());
    assert!(optional_count(Some("70000")).is_err());
    assert!(optional_count(Some("")).is_err());
    println!("PASS: borrow repaired; valid, missing, malformed, overflow, empty inputs");
}

Compile and run the repaired version

The corrected program prints its PASS message only after all assertions succeed. The compile-failing program is a separate file, so an expected diagnostic does not get confused with the successful executable.

rustc --version
rustc --edition=2024 borrow_bad.rs
# The preceding compile is expected to fail.
rustc --edition=2024 record_lab.rs -o record_lab
./record_lab

3. Do you always need braces to end a borrow?

No. This example uses first for the last time before push even though the surrounding lexical scope continues. Rust can determine that the reference is no longer used at the conflicting operation. Braces can help express a short scope, but claiming they are always required would misdescribe this verified repair.

Follow-up practice: Explain the difference between a variable still being in scope and its reference needing to remain usable across a later operation.

4. What are the three meaningful outcomes of optional_count?

Ok(Some(18)) means a present value parsed successfully. Ok(None) means no value was supplied. Err(...) means a value was supplied but was not a valid u16, including the tested text, empty string and overflow cases. Returning None for every failure would erase information the caller may need to report a bad record.

Follow-up practice: How would a user-facing importer distinguish a blank optional field from a malformed value? Decide the input-normalization policy explicitly instead of silently treating every error as absence.

5. What does transpose do in this parser?

The map call parses only a present string, producing Option<Result<u16, ParseIntError>>. Transpose rearranges that into Result<Option<u16>, ParseIntError>: absence becomes Ok(None), a successful parse becomes Ok(Some(value)), and a parse error remains an error. It changes the arrangement of the types without discarding the failure.

Follow-up practice: Write out the result for None, Some("18") and Some("eighteen") before running the assertions. Identify which case never invokes parsing.

6. Why use unwrap in the checks but return Result from the parser?

The test program uses unwrap only where it deliberately asserts that a specific input should succeed. An unexpected error makes that check fail loudly. The parser itself returns the error to its caller rather than panicking or substituting zero. The calling application still needs to decide how to report or handle that error.

Follow-up practice: Why would replacing every failed parse with zero be a behavior change? Give an input for which zero and invalid data mean different things.

Practice the explanation as a short debugging answer

Walk through the failure and repair before talking about general ownership rules. Then explain the parser’s behavior with the actual input cases, rather than only reciting type definitions.

Original interview answer and extension

Answer: “The element borrow was still used after push, so it overlapped the vector mutation. I moved its last use before push and kept the same owned vector. Separately, my parser uses Result<Option<u16>, ParseIntError> so missing input remains different from invalid input.”

Weak answer: “I cloned everything to make the compiler happy.” Diagnosis: it does not explain the original borrow or whether copying is needed for the intended behavior.

Extension: decide whether surrounding whitespace should be accepted. The current function passes the string directly to parse; adding trim would change its input contract and needs its own assertions.

Your debugging notes: owner [ ]; reference created [ ]; conflicting operation [ ]; last reference use [ ]; minimal repair [ ]; valid/missing/error cases [ ]; boundary you would add [ ].

Questions, answered

Does this example use unsafe code?

No. The repair changes the order of ordinary safe operations, and the parser uses standard-library types and methods.

Does Result<Option<T>, E> mean the same thing as Option<T>?

No. The Result preserves a failure outcome separately from successful absence. The example tests both cases explicitly.

Will every Rust version print identical diagnostic wording?

No. The exercise records Rust 1.90.0 and edition 2024. The error code and explanation are useful, but exact formatting and wording can change.

Sources and examples

ResumizeAI team. Examples are fictional teaching material, not customer results. Replace sample details with your own facts.

Your experience. A complete resume.

Build in Resumize with ATS-friendly templates, guided editing and a live preview. Start free, then upgrade when you need more.

Free account required. Free-plan PDF exports include a watermark.