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.
Ownership and borrowing
10 questions · 0 Seen10 How do iter, iter_mut, and into_iter differ on a Vec? reveal ▾ hide ▴
For a Vec
21 How do you choose among Box, Rc, Arc, and Weak? 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.
29 What do lifetime annotations express, and what can they not change? 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.
33 Why is a Rust lifetime not simply the lexical scope of a reference variable? 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.
37 How do ownership, move semantics, Copy, and Drop fit together in Rust? 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.
41 What does it mean when Rust says a value was moved, and can that binding be used again? 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.
47 How can a pattern move part of a value, and how do you avoid an accidental partial move? 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.
49 What exactly does Pin prevent from moving? reveal ▾ hide ▴
Pin constrains the pointee reached through a pointer, not the pointer handle itself. Moving a Pin<Box
57 What makes a Rust type a smart pointer, and is Deref a formal requirement? 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.
Borrow analysis
1 question · 0 Seen02 How do non-lexical lifetimes change the way you read a borrow conflict? 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.
Lifetimes
1 question · 0 Seen03 What do lifetime annotations express, and what can they not do? 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.
Disjoint access
1 question · 0 Seen04 How do you safely obtain mutable access to two elements selected by runtime indices? 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.
Standard collections
1 question · 0 Seen05 How do you choose between HashMap and BTreeMap? 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.
Ownership and allocation
1 question · 0 Seen06 Why can growing a Vec conflict with an existing element reference? 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.
Map updates
1 question · 0 Seen07 What problem does HashMap::entry solve, and when does or_insert_with matter? 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.
Set semantics
1 question · 0 Seen08 How would you deduplicate values while preserving first-seen order? 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.
Language core
4 questions · 0 Seen09 What is the difference between Iterator and IntoIterator? 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.
13 How do a Cargo package, a crate, and a module differ? 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.
17 How do String, str, and &str differ? 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.
45 What does exhaustive matching guarantee, and when can a wildcard weaken that guarantee? 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.
Evaluation model
1 question · 0 Seen11 How do laziness and short-circuiting affect an iterator pipeline? 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.
Error handling
1 question · 0 Seen12 How do you collect parsed values without losing the first error? reveal ▾ hide ▴
Map each input to Result<T, E> and collect the iterator into Result<Vec
Modules and paths
1 question · 0 Seen14 How does Rust map mod declarations to files, and what role does use play? 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.
Visibility and APIs
1 question · 0 Seen15 Why can a pub item remain unreachable, and how does pub use help? 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.
Crate boundaries
1 question · 0 Seen16 How should binaries and integration tests use a library crate in the same package? 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.
UTF-8 text
1 question · 0 Seen18 Why does Rust reject integer indexing on strings, and what does len return? 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.
API design
4 questions · 0 Seen19 How do you choose string parameter and return types for a Rust API? 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
26 How do you choose a closure bound for a callback parameter? 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.
34 How do you avoid over-constraining a function that returns a borrowed value? 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.
38 How do you choose among T, &T, and &mut T for a function parameter? 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.
Unicode text
1 question · 0 Seen20 What is the difference between bytes, Unicode scalar values, and grapheme clusters? 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.
Concurrency
1 question · 0 Seen22 Why does Arc not automatically make its inner value thread-safe? 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
Ownership graphs
1 question · 0 Seen23 How does Weak break an Rc or Arc ownership cycle? 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.
Shared ownership internals
1 question · 0 Seen24 How do get_mut and make_mut differ on Rc and Arc? 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.
Closures
1 question · 0 Seen25 How do capture modes differ from the Fn, FnMut, and FnOnce traits? 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.
Type erasure
1 question · 0 Seen27 When should a function return impl Fn, and when should it return Box<dyn Fn>? 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
Lifetimes and threads
1 question · 0 Seen28 Why does adding move not necessarily satisfy a static callback requirement? 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.
Function signatures
1 question · 0 SeenStatic boundaries
1 question · 0 Seen31 How do a static reference and a static type bound differ? 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.
Generic bounds
1 question · 0 Seen32 When does a higher-ranked trait bound solve a lifetime problem? reveal ▾ hide ▴
Use a higher-ranked trait bound when the callee, rather than the outer caller, must choose a fresh lifetime for each invocation. A bound such as F: for Fn(&a str) -> &a str requires F to preserve the input-output relationship for every suitable borrow duration. This appears when a method borrows its own local data and calls a stored generic function, because that borrow cannot be named at the outer API boundary. A single ordinary lifetime parameter would select one region too early. Common function-pointer elision can imply the same relationship, but explicit for syntax makes complex generic contracts reviewable.
Type system
1 question · 0 Seen35 Why is a mutable reference invariant in the type it refers to? 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.
Trait objects
1 question · 0 Seen36 Why can Box<dyn Trait> impose a static requirement even in a short block? 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
Ownership diagnostics
1 question · 0 Seen39 Why is adding clone not a complete fix for an ownership error? 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.
Destruction
1 question · 0 Seen40 What is a partial move, and why are Drop types restricted? 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.
Patterns and fields
1 question · 0 Seen42 What remains usable after a partial move, and why do Drop types impose a restriction? 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.
Copy and Clone
1 question · 0 Seen43 Why is “stack values copy and heap values move” a bad model for Rust? 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.
Diagnostics
1 question · 0 Seen44 How do you diagnose E0382 without defaulting to clone? 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.
Patterns and control flow
1 question · 0 Seen46 How do refutable and irrefutable patterns determine whether to use let, if let, or let-else? 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.
Match semantics
1 question · 0 Seen48 What two mistakes commonly occur with match guards and bare identifiers in patterns? 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.
Type contracts
1 question · 0 Seen50 How do Pin and Unpin interact? 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.
Unsafe construction
1 question · 0 Seen51 How do you safely construct a self-referential pinned value? 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
Projection and destruction
1 question · 0 Seen52 What must a pinned field projection prove? 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.
Interior mutability
1 question · 0 Seen53 How do Cell<T> and RefCell<T> differ, and can Cell<T> hold a non-Copy value? 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.
Runtime borrowing
1 question · 0 Seen54 When should RefCell code use try_borrow_mut instead of borrow_mut? 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.
Shared ownership
1 question · 0 Seen55 What does Rc<RefCell<T>> provide, and why is Arc<Mutex<T>> not a mechanical replacement? 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.
Reentrancy
1 question · 0 Seen56 Why do reentrant callbacks often expose RefCell borrow bugs, and how do you design around them? 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.
Pointer behavior
1 question · 0 Seen58 When should a custom Rust type implement Deref, and what does deref coercion preserve? 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.
Resource management
1 question · 0 Seen59 What can Drop guarantee, and which cleanup should remain explicit? 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.
Address stability
1 question · 0 Seen60 Why does Box<T> not by itself guarantee that T is pinned? 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
No questions match this filter.