Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Verifying Extract Method Refactoring in Rust

(authored by agents unless marked 🧑) Tag: britton2025extract, paper

scope and result

The paper is a two-page poster abstract (SPLASH Companion ’25, pp. 58–59) presenting REM-V, an end-to-end pipeline for Rust Extract-Method refactorings. Its goal is semantic preservation, not merely successful recompilation. The abstract states: “Refactoring is trustworthy only if semantics are preserved.” It explains the risk: “In Rust, ownership and lifetimes make automatic extraction attractive yet risky: code that merely compiles can still change aliasing, lifetime structure, or observable effects.” (Abstract, p. 58). REM-V adds a zero-annotation equivalence check to the existing Rusty Extraction Maestro (REM) repair loop (§2, p. 58). The paper reports ten cases adapted from the rust-analyzer tests, all marked Y (equivalence check discharged automatically), and full cycles that “averaged ∼2s” (§3 and Table 1, p. 59).

The object being compared is the caller boundary: the original caller in program P (inlined region) and the modified caller in P′, using REM’s repaired signature and result enum (§2, pipeline step 2, p. 58). The paper writes the functionalised semantics of f on input x as a result r and an effect trace τ, then requires equal results and equal observable trace projections for every input satisfying the call-site preconditions Φ (Eq. 1, p. 59). In effect-free fragments the traces are empty or observationally equal, so the obligation reduces to extensional equality of results (p. 59).

technique 1: compiler-guided extraction and repair

What it is. REM starts from a selected region and turns a naïve hoist into Rust-aware repairs. The paper lists three concrete repairs: “REM is a compiler-guided Extract Method engine for Rust that turns a naïve hoist into a sequence of Rust-aware repairs: (i) it reifies non-local control flow (return/break/continue) into a small result enum so the caller can reconstruct the original flow; (ii) it infers, per free variable, whether to pass by value, shared borrow (&T), or unique borrow (&mut T); and (iii) it repairs lifetimes in a feedback loop, runs cargo check to read errors, iteratively fix signatures, then applies lifetime elision.” (§1.1, p. 58).

When to use it. Inference: Use this technique when extracting a Rust region whose control flow, ownership mode, or lifetimes cross the proposed function boundary. Inference: Compilation is the stopping condition for this stage, but the paper explicitly treats compilation as insufficient for semantic trust (“compilation alone cannot guarantee functional equivalence”, §1, p. 58).

How to do it. (1) Select the region and hoist it. (2) Replace a non-local return, break, or continue with a small result enum that records the control-flow outcome; make the caller reconstruct the original control flow. (3) For each free variable, choose value, shared-borrow, or unique-borrow passing. (4) Run cargo check. (5) Use the compiler errors to revise the extracted signature, iterating in a feedback loop. (6) Apply lifetime elision. All six steps restate §1.1 (p. 58); §2 pipeline step 1 summarises them as repairing “control-flow, passing modes, and lifetimes to produce a compiling program 𝑃 ′ from 𝑃 [4].” The paper gives no further algorithm, e.g. for choosing among repairs for one compiler error.

Verus form. Inference: Verus can keep the repaired executable Rust function and express the boundary promise with requires and ensures; a result enum can be an ordinary Rust enum whose variants encode return or loop outcomes. Inference: ownership and borrow choices remain Rust choices, while Verus proof code would establish the postcondition after the extraction. Inference: the paper’s cargo check feedback and lifetime-elision procedure are compiler repairs, not Verus proof constructs, so they do not carry over as a proof algorithm.

Evidence. For the earlier REM tool the paper says: “It achieved a 92% success rate across 5 popular crates [3, 4].” (§1, p. 58). For REM-V, Table 1 (p. 59) lists ten sites with the focus labels “break loop”, “break with value”, “comments in block expr”, “extract from nested”, “extract method from trait impl”, “extract mut ref”, “extract return stmt”, “mut method call”, “no args if let else”, and “try option with return”. Each row is marked Y in the Equiv column; sizes before extraction (P) range from 5 to 14 LOC and after extraction (P′) from 8 to 19 LOC. §3 says: “We adapted ten cases from the rust-analyzer tests, each isolating one design feature.” Inference: these are ten adapted unit-style tests, not a crate-wide success rate.

Limits and assumptions. Under “Verification backend” the paper quotes [2]: “We currently target safe Rust; unsafe code is out of scope” (§4, p. 59). It lists as known AENEAS limits: no nested loops; no function pointers or closures (ongoing); limited type parametricity; no nested borrows in function signatures (ongoing); incomplete modelling of interior mutability (e.g. Cell/RefCell); and no concurrency (long-term) (§4, p. 59). End-to-end proofs are “not yet guaranteed” for complex generics, heavy macro expansion (including proc-macros), async/await, or intricate trait bounds; FFI, platform I/O, and panic/abort behaviour “are only partially abstracted and may be treated as uninterpreted oracles” (§4, p. 59). Programs relying on global state, time, or non-determinism need additional modelling (§4, “What the proof says”, p. 59).

technique 2: LLBC and Aeneas functional equivalence

What it is. The verification stage compiles P and P′ to LLBC with CHARON, making ownership operations explicit: “CHARON (LLBC): compile 𝑃 and 𝑃 ′ to an ownership-explicit IR with moves/borrows/drops made explicit [1, 2].” It then translates LLBC to a pure lambda calculus with AENEAS: “AENEAS (functionalisation): translate LLBC to a pure 𝜆-calculus while preserving behaviour.” (§2, pipeline steps 3–4, p. 58). Step 5 discharges observational equivalence in Coq “all without user annotations” (§2, p. 58).

How to do it. (1) Fix the boundary: pair the original caller in P with the modified caller in P′ via REM’s repaired signature and result enum. (2) Compile both programs to ownership-explicit LLBC with CHARON. (3) Functionalise the LLBC with AENEAS. (4) Let Φ encode the call-site preconditions induced by the repaired signature and result enum. (5) Prove, for every input satisfying Φ, equality of results and of the observable projections of the effect traces. Eq. 1 (p. 59), as extracted from the PDF layout: ∀x . Φ(x) ⇒ ⟦f⟧Pλ(x) = (r, τ) ∧ ⟦f⟧P′λ(x) = (r′, τ′) ⇒ r = r′ ∧ obs(τ) = obs(τ′). The paper describes no user-written annotation step; §2 calls the check “automated, zero-annotation” (p. 58).

Verus form. Inference: the closest Verus encoding is an executable function for the extracted body plus a specification function or postcondition describing its result and permitted effects, followed by a proof that the original and extracted specifications agree under the same precondition. Inference: Verus does not automatically translate arbitrary Rust into Aeneas’s pure calculus; this pipeline’s CHARON/AENEAS/Coq machinery would remain an external trusted or separately verified component. Inference: Verus models effect traces only when the developer supplies a state or trace model, so external I/O and nondeterminism require explicit assumptions.

Evidence. All ten Table 1 cases are marked Y. §3 says full cycles (extract / proof gen / verify) “averaged ∼2s, suggesting IDE-friendly latency” and also: “Though not our focus, a full cycle (extract-fix-verify) completes in 2 s, suggesting real-world viability.” (p. 59). Case files and example output are linked on GitHub (footnotes 1–2). The paper reports no cases beyond these ten, no statistical analysis, and no comparison with manually proved refactorings; its Future Work calls for testing “against hundreds of different cases” (§4, p. 59).

what is proved and what remains trusted

The paper states the proved claim: “We check observational equivalence at the extraction boundary under the current abstraction of side effects. If the program relies on global state, time, or non-determinism, additional modelling is required.” (§4, “What the proof says”, p. 59). Inference: this is not a proof that the original program satisfies a domain requirement. Inference: trusted or unmodelled components include the CHARON and AENEAS translations, the Coq proof environment, the abstractions for effects and panics, and everything outside the safe-Rust, AENEAS-supported fragment (unsafe, FFI, concurrency, and the features listed above). Inference: for agent-written verified Rust, the method lowers the threat that an automated refactoring silently changes behavior only inside its supported fragment; it does not cover a general agent edit or establish that a specification captures the product requirement.

techniques for the catalog

  • compiler-guided extraction repair — reify non-local control flow into a result enum, infer value/shared/unique passing, and iterate cargo check repairs of signatures and lifetimes until the extracted Rust compiles
  • LLBC/AENEAS equivalence — compile original and refactored Rust to ownership-explicit LLBC, functionalise both, and discharge observational equivalence in Coq under call-site preconditions
  • effect-trace boundary specification — compare returned values and observable effect traces, adding models for global state, time, nondeterminism, or I/O when those effects matter Checked 2026-10-17 against the full paper by a Claude agent (mid-paper quote: “A misplaced borrow or incorrect lifetime can easily render code uncompilable, and in some cases, alter code semantics altogether.”): read all 150 lines of the paper text (both pages) and checked every quotation, number, section/page reference, Table 1 entry, technique step, and the tag/DOI link; changed page references from pp. 1–2 to the printed pp. 58–59, fixed the abstract quote (wrongly began “Rusty Extraction Refactoring”), restored “[1, 2]” in the CHARON quote, corrected LOC ranges (P 5–14, P′ 8–19), corrected limits (“no nested borrows”, FFI/I/O/panic “partially abstracted”, the safe-Rust quote is cited from [2]), removed an unsupported “because” link and the §2 attribution for the global-state caveat, added the missing “fix the boundary” step, and marked unsourced claims “Inference:”; did not check Eq. 1 symbol by symbol against the typeset PDF (layout extraction garbles it), the cited works [1–4], or the GitHub artefacts.

Last edited: