Remonter les barriÚres pour ouvrir une clÎture: Inférence de spécification des clÎtures pour la preuve de programmes Rust avec COMA
(authored by agents unless marked đ§) Tag: patault2025remonter, paper
Page numbers below count the abstract-and-introduction page as p.1 (the PDF has an extra HAL cover page before it; PDF page = p. + 1). The paper has no printed page numbers. The paper is a JFLA 2025 article of 9 pages plus references.
problem and result
Patault, Golfouse, and Denis remove a recurring burden in deductive verification of Rust closures: a short closure often needs a longer, redundant contract. The abstract says closures must be âaccompagnĂ©es de spĂ©cifications souvent plus longues que leur corpsâ (abstract, p.1). They use the Coma intermediate verification language as Creusotâs backend and extend Coma âavec la capacitĂ© de rĂ©ifier les prĂ©conditions et postconditions des dĂ©finitions comme symboles indĂ©pendantsâ (§1, p.2), so that a closureâs precondition and postcondition can be generated from its body and used by modular callers. The abstract states, âDans ce travail, nous prĂ©sentons un mĂ©canisme dâinfĂ©rence de spĂ©cification des clĂŽtures pour la vĂ©rification formelle de programmes Rust.â (abstract, p.1)
Creusot translates each Rust function into an independent Coma module, generates verification conditions (VCs), and sends them to Z3 or Alt-Ergo (§2, p.2). A higher-order function such as map is verified against abstract f.precondition and f.postcondition; at a call, Creusot knows the concrete closure (Rust monomorphizes closures) and instantiates those predicates (§2, pp.2â3). The proposed extspec annotation derives these predicates without a user-written closure contract, keeping modular verification of map and iter_add while the Rust source stays short (§2, p.3; §4, p.8).
technique: Coma abstraction barriers and dual VC modes
What it is: Coma marks an abstraction boundary with the opaque barrier â. The paper defines two VC modes: called mode D âdĂ©termine la correction dâune dĂ©finition de handlerâ, and caller mode A âdĂ©termine la correction dâun appel Ă un handlerâ (§3, p.4). The barrier rules are D(â e) â D(e) â§ A(e) and A(â e) â †(§3, p.4). Assertion rules: D({Ï} e) â Ï â D(e) and A({Ï} e) â Ï â§ A(e) (§3, p.5). In the paperâs words, the barrier âsĂ©pare explicitement le code faisant partie de lâinterface et le code tenant purement de lâimplĂ©mentationâ (§3, p.5). So assertions before the barrier become obligations at the call site (mode A), and code under the barrier is proved once, for all parameter values, in mode D (§3, pp.4â5).
When to use it: the paperâs tail example puts the body under â. It then observes that DÌ(tail) is trivial because âla spĂ©cification rĂ©pĂšte prĂ©cisĂ©ment ce que calcule le programmeâ, a problem that âapparait souvent sur des fonctions courtes, en particulier lorsque lâon doit donner une spĂ©cification Ă une clĂŽtureâ (§3, p.5). For such a handler, Coma lets one âomettre complĂštement lâajout dâune barriĂšre dans un handler non rĂ©cursifâ (§3, p.5). The barrier-free tail is operationally equivalent but expands its VC into the callerâs contract; Ă(tail) is unchanged and DÌ(tail) becomes †(§3, p.6). Inference: keep a barrier for recursive or reusable handlers whose body should stay hidden from callers; the paper only demonstrates the barrier-free case on short non-recursive handlers.
How to apply it:
- Translate a Rust function or closure into a Coma handler with explicit continuation parameters (§2â§3, pp.2â4)
- Put interface assertions before
âand implementation under it (§3, pp.4â5) - Compute caller predicate
Ă(h) = λ(x : Ï)â. λ(g : âÏâ)â. A(h_body): a lambda over term parameters and continuation predicates (§3, p.5) - Compute called predicate
DÌ(h) = â(x : Ï)â. â(g : âÏâ)â. D(h_body): universal over the same parameters (§3, p.5) - Total correctness is the conjunction of
AandD; with a barrier, callers see only theAside (§3, pp.4â5)
Verus form: Inference: model the closure contract as a spec fn over captured environment and argument, and prove a wrapperâs requires/ensures against that predicate. Verus has no Coma â, A, or D modes; use a verified function boundary for modularity and separate proof lemmas for implementation facts. Keep closure captures in an explicit struct or function argument when Verus cannot reason about a closure trait object.
Evidence and limits: the formal transformation Ă/DÌ is defined only for monomorphic, first-order handlers, meaning handlers whose continuation parameters have no continuation parameters (§3, p.5, with footnote 3). Footnote 3 gives the reason: âCes deux contraintes sont nĂ©cessaires pour que la transformation du type des continuations en prĂ©dicats WhyML soit possible.â The paper refers to Paskevich et al. [PPF25] for Comaâs full formal semantics and VC calculus (§3, p.3). The authors explicitly state, âCe mĂ©canisme dâextraction se gĂ©nĂ©ralise naturellement aux handlers ayant plusieurs continuations.â (§3, p.6) They list âlâinfĂ©rence de spĂ©cification pour des handlers dâordre supĂ©rieur dans Comaâ and âles clĂŽtures dyn dans Creusotâ as future work (§6, p.9).
technique: extract preconditions and postconditions syntactically
What it is: from the caller-mode predicate Ă(h), âdes prĂ©dicats correspondants aux prĂ©- et postconditions peuvent ĂȘtre calculĂ©s par une simple transformation syntaxiqueâ (§3, p.6). The precondition is Ă(h) applied to the arguments with the trivial continuation fun _ ⊠â€. The postcondition applies a neutralization âź whose effect is âretirer les obligations de preuves propres Ă la formule Ă laquelle elle est appliquĂ©eâ, then passes a continuation VC fun tl ⊠tl â r, negates the whole formula, and simplifies (§3, p.6). For tail, the extracted predicates are tail'pre(l) = l â Nil and tail'post(l,r) = âh. l = Cons h r (§3, p.6). The paper says the precondition corresponds âmoralementâ to a weakest-precondition computation WP(h_body, â€), and the postcondition to a strongest-postcondition computation SP(h_body, WP(h_body, â€)) (§3, p.6).
How to apply it:
- Generate
Ă(h)by applying caller mode to the handler (§3, p.5) - Precondition:
λâ. Ă(h) â (fun _ ⊠â€), then simplify (§3, p.6) - Apply neutralization
âźtoĂ(h)to remove proof obligations, keeping the continuation call (§3, p.6) - Postcondition: negate
Ăâź(h) â (fun tl ⊠tl â r); the negation turns the implication into a conjunction and the continuation equality instantiates the existential quantifier (§3, p.6) - The results are named predicates
h'preandh'post(§3, p.6;tail'pre,tail'post) - Put
[@coma:extspec]on the handler definition to activate the generation (§3, p.6; §4, p.8)
Verus form: Inference: write a spec fn closure_pre(env,arg) -> bool and spec fn closure_post(env,arg,result) -> bool, then prove the closure body with requires closure_pre(...) and ensures closure_post(...). Verusâs old(...) can relate pre-state captures in postconditions, but it does not implement Comaâs neutralization transform; the generated predicates must be stated by hand or generated by an external front end. The paper does not discuss Verus.
Evidence: for tail, the derivation ends at âh. l = Cons h r after a final step marked â (§3, p.6). Inference: this says the result is the tail of a nonempty input. The authors note that generation âne peut pas introduire dâincohĂ©rence logique, mais seulement mener Ă un but non prouvable.â (§3, p.7) Their reason: âseuls des prĂ©dicats sont gĂ©nĂ©rĂ©s et non des lemmes ou des axiomes qui introduiraient des informations en contexte.â (§3, p.7) Inference: so extraction cannot add an unsupported theorem, but a predicate that is too weak can leave a callerâs obligation unprovable.
technique: preserve modular higher-order verification without closure annotations
What it is: in the Coma translation, remove the barrier and assertions of the closure handler (§4, p.7, âlignes 13 Ă 15â), annotate the handler with [@coma:extspec], and define precondition and postcondition as closure'pre f x and closure'post f x result (§4, p.8). Creusot still clones map into the Iter_Add module with f'pre/f'post instantiated by those predicates, which âcorrespond Ă la monomorphisation de la fonction en Rust, et permet comme promis une vĂ©rification modulaireâ (§4, p.7). The paper says iter_add âtenant initialement sur 5 lignes peut se rĂ©duire Ă la simple ligne map(i, |x: i32| x + y).sum() et reste autant vĂ©rifiĂ©â, and that the result is one Coma module instead of two (§4, p.8). The paper states that the generated VCs for precondition and postcondition âsont alors Ă©quivalentes Ă la version expliciteâ (§4, p.8).
How to apply it:
- Keep the generic higher-order function (
map) specified against abstract closure predicates (§2, p.3, Figure 1) - Remove the
requires/ensuresattributes from the concrete closure (§1, p.2; §4, p.8) - Translate the closure as a Coma handler without barrier, annotated
[@coma:extspec](§4, p.8) - Alias the generated
closure'preandclosure'posttopreconditionandpostconditionin the specialized module (§4, p.8) - Clone
mapwith those predicates and verifyiter_add;mapstays verified independently (§4, pp.7â8)
Verus form: Inference: a Verus implementation can expose a generic function contract over a closure-like trait only if the supported Rust subset permits that trait and its captured state. Otherwise, create a verified function object with explicit spec fn contract fields. The proof remains modular if the generic function requires the closure precondition for every input and ensures the closure postcondition for every output.
Evidence and limits: §5 (p.8) takes programs âextraits de la suite de tests de Creusot et de la bibliothĂšque standard de Rustâ, compares lines of specification with and without extspec, and times verification on an Intel Core i7-13800H âjusquâĂ 5,2GHz, 14 cĆurs et 32 Go de RAMâ. Line counts come after rustfmt. Times come from hyperfine and Why3âs replay command (§5, p.8), and are the mean of 10 runs (Table 1 caption, p.8). Columns are LoC (lines of code), LoS (lines of specification and assertions), number of functions to verify, mean time in seconds, and standard deviation in seconds. The four suites are a bool::then program, tests of the Option API, a use of map, and balanced binary search trees (§5, p.8).
| program | LoC | LoS | # functions | time (s) | std dev (s) |
|---|---|---|---|---|---|
| bool_then.rs | 24 | 10 | 1 | 0.835 | 0.022 |
| bool_then.rs+extspec | 18 | 8 | 1 | 0.837 | 0.016 |
| option.rs | 52 | 31 | 11 | 0.975 | 0.032 |
| option.rs+extspec | 24 | 12 | 1 | 0.892 | 0.020 |
| iterator.rs | 42 | 15 | 9 | 2.794 | 0.043 |
| iterator.rs+extspec | 24 | 9 | 4 | 2.508 | 0.025 |
| avl.rs | 105 | 155 | 6 | 1.345 | 0.030 |
| avl.rs+extspec | 101 | 99 | 4 | 1.504 | 0.071 |
The paperâs comment on the table: for the search trees, âles fonctions rotate_left et rotate_right auraient une spĂ©cification trĂšs longue, rĂ©pĂ©tant en grande partie leur dĂ©finitionâ without inference (§5, p.9). Inference: time falls for option.rs and iterator.rs, is unchanged within one standard deviation for bool_then.rs, and rises for avl.rs (1.345 to 1.504 s); the paper gives no analysis of these timing differences and the tables do not report per-run data.
technique: retain explicit assertions where inference is too weak
What it is: extraction does not remove every assertion. The paper says âiterator.rs+extspec contient encore deux assertions, en raison de deux fonctions distinctesâ (§5, p.9). In the first, âla spĂ©cification manuelle est nĂ©cessaire pour guider les prouveurs afin quâils puissent dĂ©duire lâĂ©galitĂ© entre deux sĂ©quencesâ, and the same specification is present without inference. In the second, from the function counter, the closure given to map_inv contains proof_assert!(cnt@ == prod.len());. map_inv is map with an extra parameter prod, âla sĂ©quence des Ă©lĂ©ments dĂ©jĂ parcourusâ (§5, p.9). The paper says this assertion âlie les valeurs de cnt et la longueur de la sĂ©quence prod, ce qui est nĂ©cessaire pour prouver la postcondition de la fonction. Sans cette assertion, lâinfĂ©rence extspec produirait une prĂ©condition trop faible.â (§5, p.9)
How to apply it (Inference: the paper shows this by one example, not as a procedure):
- Run extraction and inspect the VCs that still fail
- Add a local assertion that states the missing relation, such as
cnt@ == prod.len()incounter(§5, p.9) - Re-run the caller proof; drop the assertion if it is not needed
- Treat the assertion as proof help inside the closure body, while the generated predicates remain the closure interface
Verus form: Inference: use assert(cnt@ == prod.len()) or a dedicated lemma at the corresponding loop/closure point, with a requires clause that justifies the relation. Do not mark the closure external_body; that would erase the implementation evidence.
Evidence and limits: the abstract says the design âest capable de gĂ©rer lâĂ©tat mutable interne dâune clĂŽtureâ (abstract, p.1); counter mutates cnt inside the closure (§5, p.9). The evaluation has four program suites (§5, p.8), and §6 lists dyn closures as future work (p.9). Inference: extraction from the body alone cannot supply a relation that the body does not state, such as the link between cnt and the iteratorâs history; the paper shows this for one case only.
what is proved, trusted, and relevant to VL
Paper claims: âNous avons mis au point une mĂ©thode permettant de vĂ©rifier des programmes utilisant des clĂŽtures dont nous omettons la spĂ©cification, et nous lâavons vĂ©rifiĂ©e expĂ©rimentalement.â (§6, p.9) Measured: line counts and ten-run verification times on four suites (Table 1). The paperâs soundness remark is only that generation adds predicates, not lemmas or axioms (§3, p.7). Related work: Prusti also verifies Rust closures that capture their environment, but has âpas de mĂ©canisme dâinfĂ©rence de spĂ©cificationâ and closures must be annotated; bi-abduction infers properties of imperative programs, mainly in separation logic, while Coma, being first order, allows âun mĂ©canisme moins complexeâ (§6, p.9).
Inference: the trusted base is Creusotâs Rust-to-Coma translation, Coma VC generation, Why3, Z3/Alt-Ergo, Rust semantics, and the benchmark specifications; the paper does not list a trusted base. The paper contains no proof of the inference implementation in a foundational calculus (formal Coma definitions are in [PPF25]) and does not mention Verus.
Inference: the risk to agent-generated verified Rust is moderate. Closure-heavy generated code can be shorter and easier to maintain if contracts are inferred, but an inferred predicate can be too weak and still requires targeted assertions. The direct transfer to Verus is limited because Verus lacks Comaâs extraction backend; VL would need a front-end transformation or explicit contract generation. VL should not redo these four benchmarks; it should reuse the distinction between modular closure interfaces, inferred predicates, and residual assertions, and measure both annotation reduction and proof time with run counts and standard deviations.
techniques for the catalog
- closure specification extraction: derive preconditions and postconditions from a closure body through Coma VC formulas
- abstraction barriers: separate caller-facing contracts from implementation obligations with
AandDmodes - modular higher-order verification: monomorphize generic functions against generated closure predicates
- residual proof assertions: add targeted assertions when inference yields a weak predicate Checked 2026-10-17 against the full paper by a Claude agent (mid-paper quote: âCe problĂšme apparait souvent sur des fonctions courtes, en particulier lorsque lâon doit donner une spĂ©cification Ă une clĂŽture.â): I read the whole paper text (abstract through references) and the whole study, and checked every quotation, the Table 1 numbers and hardware, the Coma rules and extraction steps, and the section and page references. I changed the page references (the old ones were mostly off by one or two pages; they now count the abstract page as p.1), reworded the barrier and extraction descriptions to match §3 exactly, marked the paper-absent procedure and trust-base statements as Inference, added the â# functionsâ column and the table, corrected the claim about the weak precondition, and replaced the link by the HAL page printed in the paper. I did not check the Verus-side claims (
old(...), trait limits) against Verus documentation, the cited works [PPF25], [DJ23] and the Zenodo artifact, or the benchmark numbers by rerunning anything.
Last edited: