Rust interview bank

Questions interviewers actually ask, each answered at the length you would say it aloud, with the topic to reread if you were not sure.

60 questions Junior Senior
All levels Junior Mid Senior
Reveal one by one Show all answers
Report an error

Ownership and borrowing

10 questions
01 What are Rust’s borrowing rules, and what do they prevent? Junior common reveal ▾ hide ▴

For an overlapping region, Rust permits multiple shared references or one mutable reference, without conflicting use of both. Every reference must also remain valid for each use. Shared references support observation, while a mutable reference represents exclusive access and permits mutation. These constraints prevent dangling references and unsynchronized aliasing of ordinary data. They apply to actual use ranges rather than automatically lasting to the end of a block because non-lexical lifetimes use control-flow information. Interior-mutability types preserve the model by moving some checks to runtime or synchronization guards rather than removing the rules.

Was this clear?
10 How do iter, iter_mut, and into_iter differ on a Vec? Junior common reveal ▾ hide ▴

For a Vec, iter borrows the vector and yields &T, so the collection remains available after iteration. iter_mut takes an exclusive borrow and yields &mut T, allowing in-place element changes while that borrow is active. Calling into_iter on the owned Vec consumes it and yields T, transferring each element to the caller. Choose from the ownership contract, not style. If later code still needs the vector, borrow it. If a result must own non-Copy elements, make the necessary clone or transformation explicit instead of cloning the whole collection preemptively.

read more Iterators
Was this clear?
21 How do you choose among Box, Rc, Arc, and Weak? Junior common reveal ▾ hide ▴

Start with the ownership graph. Use Box when one owner needs heap indirection, such as a recursive node or owned trait object. Use Rc when several owners share one value but all access stays on one thread. Use Arc when shared ownership crosses threads and the inner type satisfies the necessary Send and Sync bounds. Use Weak for links that need to find an object without keeping it alive, especially parent pointers and subscriber registries. Decide on mutation separately: Rc and Arc share ownership, while RefCell, Mutex, RwLock, or atomics provide the relevant mutation discipline.

Was this clear?
29 What do lifetime annotations express, and what can they not change? Mid common reveal ▾ hide ▴

Lifetime annotations express relationships among references in a type signature. They tell the compiler which inputs can supply a returned borrow and how long the caller may use it. The compiler chooses concrete regions at each call, so a shared name does not require identical lexical scopes. An annotation cannot extend an owner, move data into static storage, or make a reference to a local allocation valid after return. If a function creates the result, it normally returns an owned value. If it returns a borrow, that borrow must trace to a valid input, field, or static item.

Was this clear?
33 Why is a Rust lifetime not simply the lexical scope of a reference variable? Junior common reveal ▾ hide ▴

A lifetime is the region in which a reference may be used safely, not necessarily the whole block where its binding is visible. With non-lexical lifetimes, the compiler uses control flow and actual uses to end a borrow after its last use. The owner can continue to exist, and the reference binding can remain in lexical scope without keeping the borrow active. When diagnosing a conflict, I identify reference creation, the conflicting access, and the later use that keeps the borrow alive. Closing braces alone do not determine that timeline.

read more Lifetimes
Was this clear?
37 How do ownership, move semantics, Copy, and Drop fit together in Rust? Junior common reveal ▾ hide ▴

Each value has an owning place responsible for its eventual destruction. Assigning or passing a non-Copy value by value moves that responsibility, so the source can no longer be used. A Copy type instead duplicates implicitly and leaves the source valid; it cannot also implement Drop. Clone is separate and explicit, with semantics defined by the type. When an owner leaves scope, Rust runs Drop and then releases owned resources. I still check exceptional termination and strong reference cycles, because safe ownership prevents invalid access but does not guarantee every destructor runs.

read more Ownership
Was this clear?
41 What does it mean when Rust says a value was moved, and can that binding be used again? Mid common reveal ▾ hide ▴

A move transfers responsibility for a non-Copy value from one place to another. The compiler then treats the source place as uninitialized, so reading or borrowing it is rejected; this is a static rule rather than a runtime moved flag. The binding itself is not permanently poisoned. If it is mutable and every path assigns a fresh value before the next use, that place becomes initialized again. The new assignment does not recover the old value, which remains owned by the move destination or has already been destroyed there.

Was this clear?
47 How can a pattern move part of a value, and how do you avoid an accidental partial move? Mid common reveal ▾ hide ▴

A by-value pattern over an owned subject moves non-Copy fields into its bindings and copies Copy fields. Moving one field while leaving others initialized creates a partial move: separate remaining fields may still be usable, but the whole structure is no longer available. I start by deciding what the operation owns. For inspection, I match &value, letting match ergonomics create borrowed field bindings. For mutation, I match &mut value. I match value only when the operation should consume it. Adding clone merely to fix E0382 can duplicate data and often conceals that the function signature expresses the wrong ownership contract.

Was this clear?
49 What exactly does Pin prevent from moving? Mid common reveal ▾ hide ▴

Pin constrains the pointee reached through a pointer, not the pointer handle itself. Moving a Pin<Box> between variables or container slots moves the Box-shaped owner while leaving its heap allocation in place. For T: Unpin, safe code can recover ordinary mutable access and move T because the type declares that address stability is irrelevant. For T: !Unpin, safe APIs prevent extracting or replacing T through the pin. I therefore identify the moved object precisely: the handle, the complete pointee, or a field reached by projection.

read more Pin and Unpin
Was this clear?
57 What makes a Rust type a smart pointer, and is Deref a formal requirement? Junior common reveal ▾ hide ▴

Smart pointer is a conventional category, not a marker trait enforced by the language. The type behaves like a pointer while adding ownership, sharing, destruction, or resource-management semantics. Box, Rc, and Arc own their targets under different rules, while Weak provides a non-owning observation path. Many smart pointers implement Deref for reference-like access and Drop for cleanup, but Deref alone does not prove smart-pointer semantics. I read the constructor, clone behavior, access methods, thread bounds, and destruction contract to determine what the wrapper actually guarantees.

Was this clear?

Borrow analysis

1 question
02 How do non-lexical lifetimes change the way you read a borrow conflict? Mid common reveal ▾ hide ▴

NLL lets the compiler infer a borrow region from later uses and control flow instead of extending every borrow to its enclosing brace. A shared reference can therefore be read for the last time and then be followed by a mutable borrow in the same lexical block. When diagnosing an error, identify the reference creation, the conflicting access, and the later use that keeps the first borrow active. NLL does not make dangling references legal or guarantee that every human-obvious access pattern is accepted. It makes valid regions more precise while preserving the same shared-versus-exclusive rules.

Was this clear?

Lifetimes

1 question
03 What do lifetime annotations express, and what can they not do? Mid common reveal ▾ hide ▴

A lifetime annotation names relationships among references in a type signature. For a function returning one of two borrowed strings, a shared lifetime parameter says the result cannot be used beyond the region where the relevant inputs remain valid. The compiler chooses concrete regions at each call; the annotation does not force arguments to have identical lexical scopes. It also cannot extend an owned value, turn a local allocation into static storage, or repair a reference to data destroyed on return. Return owned data when the function creates it, and return a borrow only when it originates from a valid input or static source.

Was this clear?

Disjoint access

1 question
04 How do you safely obtain mutable access to two elements selected by runtime indices? Senior occasional reveal ▾ hide ▴

First reject equal and out-of-range indices. Directly indexing the same slice twice usually fails because the borrow checker conservatively treats the two places as possibly overlapping. Order the indices, split the slice at the larger index with split_at_mut, then take one element from each returned slice and restore the logical source and destination roles. The safe API proves that the regions do not overlap. Validate arithmetic and other domain preconditions before changing either element, so a later failure does not leave a partial update. Raw pointers are unnecessary for this ordinary case.

Was this clear?

Standard collections

1 question
05 How do you choose between HashMap and BTreeMap? Mid common reveal ▾ hide ▴

Start with the observable contract, not a blanket speed claim. HashMap models lookup by equality and hash and makes no iteration-order promise. BTreeMap requires Ord, iterates in key order, and supports range queries and first-or-last key operations. Choose BTreeMap when callers rely on ordered traversal, ranges, or reproducible key order throughout the model. Choose HashMap when those semantics are absent, then sort explicitly at a presentation boundary if only one output needs order. Also check whether the key type has a natural, consistent Ord implementation or only Eq and Hash.

read more Collections
Was this clear?

Ownership and allocation

1 question
06 Why can growing a Vec conflict with an existing element reference? Mid common reveal ▾ hide ▴

A Vec owns a contiguous buffer. When push needs more capacity, it may allocate another buffer, move the elements, and release the old allocation, which would leave an old element reference dangling. Rust therefore rejects a mutable vector operation while a reference borrowed from that vector is still needed later. Even when spare capacity happens to prevent reallocation, other mutations may shift elements or change which entity an index denotes. End the borrow before mutation, retain owned data when appropriate, or save a domain identifier and borrow again after the collection changes.

read more Collections
Was this clear?

Map updates

1 question
07 What problem does HashMap::entry solve, and when does or_insert_with matter? Mid common reveal ▾ hide ▴

entry turns the presence or absence of one key into an Occupied or Vacant state while the map is mutably borrowed. That lets counting, grouping, and conditional replacement use one coherent branch rather than contains_key followed by another lookup. or_insert returns a mutable reference to the stored value, inserting its argument for a vacant entry. The argument itself is evaluated eagerly, though, so or_insert(expensive()) still calls expensive for an occupied key. Use or_insert_with when construction should happen only on the vacant path, or or_default when Default is exactly what you need.

read more Collections
Was this clear?

Set semantics

1 question
08 How would you deduplicate values while preserving first-seen order? Mid occasional reveal ▾ hide ▴

Do not collect straight into HashSet and then iterate it, because the set removes duplicates but does not preserve input order. Keep a result Vec and a membership HashSet. Walk the input once; when seen.insert(key) returns true, append the corresponding value to the result. Decide whether seen owns keys or borrows them from input based on the result lifetime and ownership contract. If output order should instead follow the key’s Ord implementation, collecting into BTreeSet expresses a different, sorted contract. Test interleaved duplicates, not just adjacent ones.

read more Collections
Was this clear?

Language core

4 questions
09 What is the difference between Iterator and IntoIterator? Junior common reveal ▾ hide ▴

Iterator is the stateful producer that has an Item type and a next method. IntoIterator is the conversion contract used to obtain such a producer from another value. A for loop accepts an IntoIterator, calls into_iter once, and then advances the resulting iterator. One collection may provide different IntoIterator implementations for an owned value, a shared reference, and a mutable reference. Those implementations can yield T, &T, and &mut T respectively, so checking only the loop syntax is not enough; you must also inspect the receiver and Item type.

read more Iterators
Was this clear?
13 How do a Cargo package, a crate, and a module differ? Junior common reveal ▾ hide ▴

A Cargo package is the unit described by one Cargo.toml and may contain multiple build targets. Each target is a crate: one library or executable compiled as a unit, with its own crate root and module tree. A package can have at most one library crate and multiple binary crates. Modules live inside a crate and organize items, paths, and visibility; they are not independent Cargo targets. Keeping these levels separate explains why src/lib.rs and src/main.rs in one package cannot share private items or use crate:: to reach the same root.

Was this clear?
17 How do String, str, and &str differ? Junior common reveal ▾ hide ▴

String is an owned, growable buffer whose contents must be valid UTF-8. str is the dynamically sized string-slice type, so code normally uses it behind a pointer. &str is a shared borrow of valid UTF-8 bytes and carries a data pointer plus a byte length; it has no capacity and cannot grow. A literal is usually a static &str, while String::as_str returns a view into an owned buffer. Use &str for ordinary read-only parameters and String when a value must own, retain, or grow the text. Cloning a String copies its bytes; borrowing it does not.

read more Strings
Was this clear?
45 What does exhaustive matching guarantee, and when can a wildcard weaken that guarantee? Mid common reveal ▾ hide ▴

Exhaustiveness guarantees that every value represented by the matched type can select an arm; it does not guarantee that the selected behavior is correct. On a closed enum I usually name every variant, so adding one creates a compile error at every decision point that needs review. A wildcard still makes the match exhaustive, but it groups all remaining and future cases into one behavior. That is appropriate for open inputs or an external non-exhaustive enum. It is risky when the fallback ignores data, reports success, or calls unreachable, because compiler feedback becomes silent behavior or a runtime panic.

Was this clear?

Evaluation model

1 question
11 How do laziness and short-circuiting affect an iterator pipeline? Mid common reveal ▾ hide ▴

Adapters such as map and filter store an upstream iterator and closures but do not request elements themselves. A consumer drives the chain one item at a time. Consumers such as find and any stop after a decisive item, while collect normally runs to exhaustion and fallible collection stops at the first error. Consequently, later inputs may never enter any closure. This matters for both work and side effects: logging or mutation inside inspect or map can run fewer times than the source length. Tests should cover where termination occurs, not only the final value.

read more Iterators
Was this clear?

Error handling

1 question
12 How do you collect parsed values without losing the first error? Mid occasional reveal ▾ hide ▴

Map each input to Result<T, E> and collect the iterator into Result<Vec, E>. The Result implementation of FromIterator gathers every Ok value and returns the first Err immediately, so later inputs are not evaluated. The target type must be visible through an annotation, return type, or turbofish. Using filter_map with Result::ok expresses a different contract: it discards errors and retains successes. That is valid only when rejected inputs are intentionally ignorable. For imports, configuration, money, or identifiers, silent dropping usually turns corrupted input into misleading partial success.

read more Iterators
Was this clear?

Modules and paths

1 question
14 How does Rust map mod declarations to files, and what role does use play? Mid common reveal ▾ hide ▴

An inline mod name { … } defines a child module in place. A declaration such as mod catalog; defines the same logical child but loads its body from the conventional catalog.rs or catalog/mod.rs location; both candidate entry files cannot represent that module simultaneously. Child file lookup follows the logical parent, so a declaration inside catalog belongs under catalog/. Merely creating a file under src does not attach it to a crate. use operates after the module exists: it binds an existing path into one scope, but it neither declares a module nor controls which files compile.

Was this clear?

Visibility and APIs

1 question
15 Why can a pub item remain unreachable, and how does pub use help? Mid common reveal ▾ hide ▴

Visibility is checked from the use site across the complete path. Marking a function pub allows access only as far as its ancestor modules permit, so an external caller still cannot name it through a private ancestor. Making every ancestor public works but exposes implementation structure as API. pub use creates another reachable path to the existing public item, often at the crate root, while the internal module can remain private. This supports a stable facade: files and internal nesting may change without forcing callers to change imports, provided the re-exported path and behavior remain compatible.

Was this clear?

Crate boundaries

1 question
16 How should binaries and integration tests use a library crate in the same package? Mid occasional reveal ▾ hide ▴

Treat them as external consumers of the library. src/main.rs, each target under src/bin, and each integration test under tests compile as crates separate from src/lib.rs. Their crate:: paths start at their own roots, so they import the library by its crate name, such as store_domain for package store-domain. They can access only reachable pub API, not pub(crate) or private library items. Shared application logic therefore belongs in the library behind a deliberate public facade. This boundary also makes integration tests useful for proving that documented re-export paths really work for downstream callers.

Was this clear?

UTF-8 text

1 question
18 Why does Rust reject integer indexing on strings, and what does len return? Junior common reveal ▾ hide ▴

A Rust string is UTF-8, so one Unicode scalar value occupies one to four bytes. An integer index is ambiguous: it could mean a byte, scalar value, or user-perceived grapheme cluster, and finding the nth scalar is not constant time. Rust therefore provides no text[0] operation. len returns the stored byte length. Use bytes for byte-oriented protocols, chars for scalar values, and a Unicode segmentation implementation for grapheme clusters. Range slicing is still byte-based and requires both endpoints to be character boundaries. get returns None for a bad boundary, whereas direct range indexing panics.

read more Strings
Was this clear?

API design

4 questions
19 How do you choose string parameter and return types for a Rust API? Mid common reveal ▾ hide ▴

Start with ownership. A function that only reads during the call usually accepts &str, allowing literals and views into String values without allocation. Accept String when the function stores the input, sends it to another owner, or needs its growable buffer. Into can be useful at a deliberate ownership boundary but hides an allocation that callers should understand. Return &str only when the result is a view into an input or other longer-lived storage and tie the lifetime accordingly. Return String when the function creates, formats, normalizes, or otherwise delivers independent text. Lifetime annotations cannot make a local temporary survive.

read more Strings
Was this clear?
26 How do you choose a closure bound for a callback parameter? Mid common reveal ▾ hide ▴

Start with the implementation’s call count and receiver needs. If it calls at most once, accept FnOnce; this also admits FnMut and Fn closures. If it repeats calls and may allow captured state to change, use FnMut and bind the parameter mutably. Require Fn only when calls must work through shared access, such as a callback shared among readers. Then specify argument and result ownership independently: Fn(&str) and Fn(String) expose different boundaries. Choosing the strongest-looking trait is counterproductive because it rejects valid stateful or consuming callbacks without adding speed.

read more Closures
Was this clear?
34 How do you avoid over-constraining a function that returns a borrowed value? Mid common reveal ▾ hide ▴

I trace every possible returned reference to its source before naming lifetimes. If the result can come from either of two inputs, those inputs and the output need a common relationship. If it can only come from the first input, auxiliary references should have independent or elided lifetimes so temporary arguments do not restrict the result. A lifetime name is a caller-visible contract, not documentation decoration. I verify precision with a call that destroys each auxiliary argument before the returned reference’s last use; valid source data should still make that call compile.

read more Lifetimes
Was this clear?
38 How do you choose among T, &T, and &mut T for a function parameter? Junior common reveal ▾ hide ▴

I start with responsibility rather than compiler convenience. T means the function receives ownership and may retain, transfer, or destroy the value. &T gives temporary shared access and is the usual choice for a read-only operation that does not retain data. &mut T gives temporary exclusive access for in-place mutation while ownership stays with the caller. If the function must keep text received as &str, it creates an owned value at that boundary or exposes a valid lifetime relationship. I avoid taking ownership merely to read, because that forces unnecessary moves or clones on callers.

read more Ownership
Was this clear?

Unicode text

1 question
20 What is the difference between bytes, Unicode scalar values, and grapheme clusters? Mid occasional reveal ▾ hide ▴

Bytes are UTF-8 storage and I/O units. Rust char represents a Unicode scalar value, which excludes surrogate code points but may still be only part of what a user sees as one character. A grapheme cluster follows Unicode text-segmentation rules and may combine a base letter, marks, variation selectors, or joined emoji. len counts bytes and chars iterates scalar values; the standard library does not provide full grapheme segmentation or automatic normalization. Choose the unit from the domain. Buffer limits may count bytes, parser logic may inspect scalars, and cursor movement usually needs grapheme boundaries. None of these counts guarantees display width.

read more Strings
Was this clear?

Concurrency

1 question
22 Why does Arc not automatically make its inner value thread-safe? Mid common reveal ▾ hide ▴

Arc makes increments, decrements, and upgrades of the ownership counts safe across threads. It does not synchronize operations performed through T. Consequently, Arc implements Send and Sync only when T satisfies the corresponding Send and Sync requirements; Arc<RefCell> does not become a valid shared-mutation primitive. Read-only immutable data can often use Arc directly. Shared mutation needs a type whose semantics cover that access, such as Mutex, RwLock, or atomics. The distinction prevents data races while keeping ownership lifetime and mutation policy as separate design decisions.

Was this clear?

Ownership graphs

1 question
23 How does Weak break an Rc or Arc ownership cycle? Mid common reveal ▾ hide ▴

A strong Rc or Arc keeps T alive, so a closed path of strong edges can leave every count above zero after external owners disappear. Weak represents a non-owning edge: downgrade creates it without increasing the strong count, and upgrade returns an Option because the target may already be gone. Model the edge according to domain ownership, commonly strong from parent to child and weak from child to parent. For registries, store weak subscribers when registration should not extend subscriber lifetime, and prune entries whose upgrade returns None.

Was this clear?

Shared ownership internals

1 question
24 How do get_mut and make_mut differ on Rc and Arc? Senior occasional reveal ▾ hide ▴

get_mut returns a mutable reference only when no other strong or weak pointer refers to the allocation; otherwise it returns None and never clones. make_mut requires T: Clone and guarantees mutable access with copy-on-write behavior. If another strong owner exists, it clones T into an independent allocation for the current handle. If there is only one strong owner but weak pointers remain, make_mut may dissociate those weak pointers instead, after which they cannot upgrade. Neither an earlier strong_count observation nor value equality proves the uniqueness needed by get_mut.

Was this clear?

Closures

1 question
25 How do capture modes differ from the Fn, FnMut, and FnOnce traits? Mid common reveal ▾ hide ▴

A capture mode describes how an outside place enters the closure: through a shared, unique immutable, or mutable borrow, or by value. The call traits describe what a call does with the stored environment. Every closure implements FnOnce; one that does not move out a capture also implements FnMut, and one that neither moves out nor mutates captures also implements Fn. The distinction matters because move changes capture mode but does not force FnOnce. A move closure that only reads an owned String may still implement Fn and be called repeatedly.

read more Closures
Was this clear?

Type erasure

1 question
27 When should a function return impl Fn, and when should it return Box<dyn Fn>? Senior occasional reveal ▾ hide ▴

Return impl Fn when every control-flow path produces one concrete closure type. It hides that unnameable type while preserving static dispatch and avoiding an obligatory allocation. Separate closure expressions have distinct types, so two unrelated branch closures cannot directly satisfy one opaque return type. Use Box when runtime choice or a heterogeneous collection needs type erasure and owned storage. At that boundary, state the object lifetime and any Send or Sync requirements. If every candidate captures nothing, a function pointer may be simpler; a closed set of behaviors may be clearer as an enum.

read more Closures
Was this clear?

Lifetimes and threads

1 question
28 Why does adding move not necessarily satisfy a static callback requirement? Mid common reveal ▾ hide ▴

move transfers each captured place into the closure, but the captured value may itself be a reference. Moving an &str copies that reference; it does not copy or extend the lifetime of the text behind it. A static bound requires the closure type to contain no borrow that expires too early for the storage or thread boundary. I trace every capture to its owner, then move an owned String, Vec, Arc, or another suitable value when the callback must be independent. Mechanically adding static cannot lengthen a local, and broad cloning can hide a poor ownership design.

read more Closures
Was this clear?

Function signatures

1 question
30 What does using the same lifetime name for two inputs and an output really mean? Mid common reveal ▾ hide ▴

It means there must be a caller-chosen region in which both input borrows are valid and in which the returned reference may be used. The owners do not need identical lexical scopes; a longer borrow can be shortened to the common region. The signature also permits the implementation to return either input, so the result is conservatively limited by both. If the implementation can only return the first input, giving the second a different or elided lifetime is more precise. That prevents an unrelated short-lived argument from needlessly restricting the returned reference.

Was this clear?

Static boundaries

1 question
31 How do a static reference and a static type bound differ? Mid common reveal ▾ hide ▴

A static reference points to data guaranteed valid for the entire program, as with string literals and static items. A T: static bound instead says that values of T contain no borrowed data that expires sooner. An owned String satisfies that bound because it contains its allocation by ownership, yet the String can still be dropped at any ordinary scope boundary. Moving a reference into a closure does not make its referent static. Thread and stored-callback APIs often need the type bound, so callers should move owned data or appropriate shared ownership rather than leak memory or forge a lifetime.

Was this clear?

Generic bounds

1 question
32 When does a higher-ranked trait bound solve a lifetime problem? Senior occasional reveal ▾ hide ▴

Type system

1 question
35 Why is a mutable reference invariant in the type it refers to? Senior occasional reveal ▾ hide ▴

A mutable reference permits both reading and writing, so allowing arbitrary subtype replacement would break the type promised to later readers. If &mut &‘static str could be treated as &mut &‘short str, code could write a short-lived reference through it. After that borrow ended, the original location could still be read under the false assumption that it contains a static reference. Rust therefore makes &mut T invariant in T, while it remains covariant in the mutable reference’s own lifetime. This distinction is central when nested references carry different lifetimes.

read more Lifetimes
Was this clear?

Trait objects

1 question
36 Why can Box<dyn Trait> impose a static requirement even in a short block? Senior occasional reveal ▾ hide ▴

A trait object has an object lifetime bound that limits borrows hidden inside the erased concrete type. When that bound is omitted, Rust applies object-lifetime defaulting rules; an owning position such as Box commonly defaults to ‘static. The box variable’s short lexical scope does not replace that type-level default. If the concrete object intentionally borrows data, expose Box<dyn Trait + ‘a> and carry that lifetime in the container. If the object must be independent, make the concrete value own its data instead of leaking or forging a lifetime.

read more Lifetimes
Was this clear?

Ownership diagnostics

1 question
39 Why is adding clone not a complete fix for an ownership error? Mid common reveal ▾ hide ▴

Clone changes the program by creating another logical owner or value; it does not merely silence the compiler. For String or Vec it usually duplicates owned data, while for Rc or Arc it creates another shared handle and changes destruction timing. The right question is whether the contract truly needs independent ownership. I first trace the move and ask whether the callee only reads, mutates temporarily, retains, or consumes the input. A borrow or deliberate move often expresses that answer better. If a clone remains, I document what it copies and measure it when the cost matters.

read more Ownership
Was this clear?

Destruction

1 question
40 What is a partial move, and why are Drop types restricted? Senior occasional reveal ▾ hide ▴

A partial move transfers ownership of one non-Copy field while leaving other fields initialized. The compiler permits separate use of the remaining fields but rejects use of the whole struct because it is incomplete. A type implementing Drop is more restricted: its destructor receives &mut self and may rely on every field satisfying the type invariant, so arbitrary field extraction is rejected. I borrow fields when observation is enough. When extraction is part of the design, I consume and destructure the whole value or store the field in Option and use take, leaving a valid state for Drop.

read more Ownership
Was this clear?

Patterns and fields

1 question
42 What remains usable after a partial move, and why do Drop types impose a restriction? Mid common reveal ▾ hide ▴

A partial move takes one non-Copy field while leaving other fields initialized. Those remaining fields may still be accessed separately, but the struct cannot be used as a complete value because one part is absent. Copy fields are copied rather than moved. A type implementing Drop is stricter: its destructor receives mutable access to the whole value and may rely on every field satisfying the invariant, so Rust rejects arbitrary field extraction. When extraction is intentional, consume the whole value or store the field in Option and use take to leave a valid empty state.

Was this clear?

Copy and Clone

1 question
43 Why is “stack values copy and heap values move” a bad model for Rust? Junior common reveal ▾ hide ▴

Storage location does not decide whether assignment leaves the source usable. The Copy trait does. Shared references can be Copy even when they point into heap storage, while a fixed-size stack value can be non-Copy because it owns a resource or implements Drop. Copy permits implicit duplication with no custom method call. Clone is explicit and type-defined: String cloning duplicates its buffer, but Rc cloning creates another owner of the same allocation. I inspect the concrete trait implementation and resource meaning instead of guessing from a value’s shape or size.

Was this clear?

Diagnostics

1 question
44 How do you diagnose E0382 without defaulting to clone? Mid common reveal ▾ hide ▴

I start at the earlier “value moved here” note, not the later failed use. I inspect whether the move came from assignment, a by-value parameter, a consuming method, iteration, or a pattern. Then I state what the later operation needs: ownership, shared reading, or exclusive mutation. That decides whether to keep the move, borrow temporarily, change the signature, return ownership, or reinitialize the source. Only if two values or handles are part of the domain contract do I clone, after checking what that type’s Clone implementation actually duplicates or shares.

Was this clear?

Patterns and control flow

1 question
46 How do refutable and irrefutable patterns determine whether to use let, if let, or let-else? Junior common reveal ▾ hide ▴

An irrefutable pattern matches every value of its type, so ordinary let, function parameters, and for bindings can use it without a failure branch. A refutable pattern, such as Some(value), a literal, or a fixed slice shape, can fail. Use if let when only the successful shape needs a local block and failure may do nothing. Use let-else when failure should leave early and the successful bindings must remain available afterward; its else branch must diverge through return, break, continue, or a never-returning expression. Use match when several outcomes carry meaning or exhaustiveness should remain visible.

Was this clear?

Match semantics

1 question
48 What two mistakes commonly occur with match guards and bare identifiers in patterns? Mid occasional reveal ▾ hide ▴

First, a guard does not contribute to exhaustiveness. Some(x) if x > 0 covers only values for which the guard is true, so another Some arm is still needed before None. A guard after A | B applies to the complete or-pattern. Second, a bare lowercase identifier normally creates a new binding; it does not compare with an outer variable of the same name. That binding can match every value and make later arms unreachable. To compare runtime values, bind a fresh name and use a guard such as value if value == expected. Treat unreachable-pattern warnings as evidence, not noise.

Was this clear?

Type contracts

1 question
50 How do Pin and Unpin interact? Mid common reveal ▾ hide ▴

Pin provides a no-move interface, while Unpin says a type does not need that restriction. Most types implement the auto trait because moving them cannot invalidate internal state. A type can opt out by containing PhantomPinned or another !Unpin field. Pin<&mut T>::get_mut is safe only for T: Unpin; get_unchecked_mut is available for other types but transfers the proof obligation to unsafe code. PhantomPinned does not pin a value or initialize self-references by itself. It only prevents the automatic Unpin implementation, so construction and projection still need a complete invariant.

read more Pin and Unpin
Was this clear?

Unsafe construction

1 question
51 How do you safely construct a self-referential pinned value? Senior occasional reveal ▾ hide ▴

Use two phases. First create ordinary fields with internal pointers empty, place the complete value in final storage with Box::pin or another valid pinning owner, and only then initialize pointers to that storage through a narrowly justified unsafe operation. The public constructor returns Pin<Box> rather than movable Self. The type opts out of Unpin and exposes no safe method that moves a structurally pinned field. I also review partial initialization, panic cleanup, and Drop, because the address must stay valid until destruction finishes, not merely until construction returns.

read more Pin and Unpin
Was this clear?

Projection and destruction

1 question
52 What must a pinned field projection prove? Senior occasional reveal ▾ hide ▴

A projection from Pin<&mut Parent> to Pin<&mut Field> must show that the field stays at a stable location whenever the parent is pinned, that the returned borrow cannot outlive the parent access, and that no other safe path can move the field. The same structural choice affects Unpin and Drop: a parent cannot promise Unpin unconditionally when a pinned field needs stability, and its destructor cannot extract that field before destruction. Pin::map_unchecked_mut is unsafe because the library cannot verify those facts from the closure, so generated projection helpers deserve safety-boundary review.

read more Pin and Unpin
Was this clear?

Interior mutability

1 question
53 How do Cell<T> and RefCell<T> differ, and can Cell<T> hold a non-Copy value? Mid common reveal ▾ hide ▴

Cell never hands out an ordinary reference to its contents through shared access. Its get method therefore requires Copy, but the wrapper itself does not: set and replace work with values such as String, take works when T implements Default, and into_inner consumes the cell. RefCell instead returns Ref and RefMut guards and dynamically enforces the usual shared-versus-exclusive borrowing rule. Conflicting borrow calls panic, while try_borrow variants return errors. I choose Cell when whole-value replacement expresses the operation and RefCell only when callers must borrow or mutate inner structure.

Was this clear?

Runtime borrowing

1 question
54 When should RefCell code use try_borrow_mut instead of borrow_mut? Mid common reveal ▾ hide ▴

borrow_mut is appropriate when a conflicting borrow would prove a logic bug; it panics if any Ref or RefMut guard is still active. try_borrow_mut performs the same immediate check but returns BorrowMutError, so it fits an API where temporary unavailability is an expected state that the caller can handle without waiting. This is not lock contention because RefCell is not shared concurrently through Sync. I do not replace borrow_mut mechanically and discard the error: that can turn a visible defect into a lost update. I first trace the lifetime of every guard and each possible reentrant call.

Was this clear?

Shared ownership

1 question
55 What does Rc<RefCell<T>> provide, and why is Arc<Mutex<T>> not a mechanical replacement? Mid common reveal ▾ hide ▴

Rc provides several owning handles in one thread, while RefCell lets those handles request shared or exclusive access checked at runtime. The combination is useful only when both properties match the domain. Rc is neither Send nor Sync, and RefCell is not Sync, so cloning the handle cannot make it cross a multithreaded boundary. Arc supplies atomic reference counting and Mutex supplies mutual exclusion, but they add blocking, poisoning policy, critical-section size, and lock-order questions. I first consider single ownership or message passing, then choose synchronization only when threads genuinely need direct shared access.

Was this clear?

Reentrancy

1 question
56 Why do reentrant callbacks often expose RefCell borrow bugs, and how do you design around them? Senior occasional reveal ▾ hide ▴

A guard created for an iterator or mutable update can remain alive while user code runs. If a callback reenters the object and borrows the same RefCell incompatibly, the nested borrow panics even though execution is single-threaded. I prepare owned inputs first, perform the state transition in a short guard, release it, and then call external code. Registries often clone a snapshot of Rc callback handles before dispatch. That choice also defines semantics: additions and removals usually become visible on the next dispatch. Tests should exercise actual subscribe, unsubscribe, and nested-dispatch paths rather than only two adjacent borrow calls.

Was this clear?

Pointer behavior

1 question
58 When should a custom Rust type implement Deref, and what does deref coercion preserve? Mid common reveal ▾ hide ▴

I implement Deref when the wrapper is meant to substitute transparently for one stable target type, and exposing that target’s methods will not bypass domain invariants. Deref coercion converts references along the Target relationship; it borrows the target and does not move it, clone it, or add an Rc or Arc owner. Method lookup may apply the conversion implicitly, so the implementation should be cheap and unsurprising. For identifiers, validated values, and secrets, a named method such as as_str or expose is usually clearer because it keeps the intended API boundary visible.

Was this clear?

Resource management

1 question
59 What can Drop guarantee, and which cleanup should remain explicit? Mid common reveal ▾ hide ▴

Drop gives a type a cleanup hook when normal Rust destruction reaches a value. std::mem::drop can end that lifetime early by consuming the value, but code cannot call the Drop method directly. The method returns no Result, and destruction can be skipped or delayed by leaks, mem::forget, strong-reference cycles, or process aborts. I use Drop for short, infallible local release such as relinquishing a guard. Commits, flushes, remote acknowledgements, and any operation whose failure must reach the caller need an explicit method returning a result, with Drop only as fallback.

Was this clear?

Address stability

1 question
60 Why does Box<T> not by itself guarantee that T is pinned? Senior occasional reveal ▾ hide ▴

Moving a Box usually moves only its pointer, so the current heap allocation often stays at the same address. That observation is not a type-system guarantee: safe operations may replace or move out T when their bounds permit, and APIs receiving an ordinary Box make no immobility promise. Address-sensitive values need Pin<Box> together with the Pin contract and the type’s Unpin behavior. I also check projections and Drop, because pinning must preserve the relevant location until destruction completes. Box provides ownership and indirection; Pin adds restrictions on moving the pointee.

Was this clear?