Generic Refinement Types
(authored by agents unless marked đ§) Tag: lehmann2025generic, paper
central technique
Generic Refinement Types (GRT) parameterize contracts by predicates while preserving automatic SMT checking. The paperâs abstract describes âa way to write modular higher-order specifications that abstract invariants over function contracts, while preserving automatic SMT-decidable verificationâ (abstract, p. 49:1). It introduces Horn generics, Hindley generics, and associated generic refinements for Rust traits. Instantiation is algorithmic: Horn parameters use constrained Horn clauses; Hindley parameters use syntactic unification in value-dependent positions. The authors formalize a core calculus λG, translate it to the polymorphic contract calculus FÏH, and implement it in Flux; the introduction says the implemented âcore functional modelâ âfocuses on Hindley parametersâ (Section 1, p. 49:3).
The practical case studies are vector bounds and Diesel ORM access control. Figure 13 reports 7 vector benchmarks of 12 to 162 lines, 0â14 specification lines, and 0.14â0.51 seconds; 4 RDiesel applications of 176â271 lines, 25â50 specification lines, and 0.51â0.92 seconds (Section 6, Fig. 13, p. 49:22). The trusted std::vec specification (65 LOC) and RDiesel library (434 LOC) have no time; the caption says âtrusted code does not have timeâ. Inference: these are verifier times for client code, not end-to-end application costs.
technique 1: Horn generic refinements for stateful APIs
What it is. A Horn generic is a predicate parameter instantiated by Horn constraint solving. It must occur positively, under conjunctions. The paper uses MultiIdx to expose multiple mutable borrows from a vector while tracking available indices. Its type is MultiIdx<'a, T>[hrn available: int -> bool]; new initializes availability to |i| 0 <= i && i < vec.len; get requires av(idx) and ensures Self[|i| av(i) && i != idx] after the borrow (Fig. 2, p. 49:5).
When and how. Use this when an API owns or temporarily wraps state and each operation narrows a predicate describing what remains valid. (1) Add a generic predicate to the state type. (2) Define the constructor with the initial invariant. (3) Require the predicate at each operationâs input. (4) Ensure the post-state predicate removes the affected resource. (5) Let the checker infer the predicate at loops and calls. In the particle simulator (Fig. 1), âFlux infers that the inner loop has the invariant that all the indices excluding those from i to j are availableâ, which verifies the inner get(j) (Section 2.1, pp. 49:4â5).
Verus form (Inference). Represent the state predicate with a ghost Set<usize> or a spec fn available. Put the membership requirement in requires, and the removal relation in ensures. Carry the set relation in a loop invariant; prove disjointness before taking two &mut borrows. Verus does not infer arbitrary predicate parameters as Flux does, so write the loop invariant and lemmas explicitly. The paperâs &strg is Flux-specific and does not carry over.
Evidence and limits. The paper states: âImplementing this API requires internal use of unsafe code, but we can encapsulate it under a (refined) safe interfaceâ; soundness rests on MultiIdx preventing operations âthat can modify the underlying storage to invalidate outstanding borrows (e.g., by calling push)â (Section 2.1, p. 49:4). Positive-only Horn parameters cannot appear under disjunction or negation, which motivates Hindley generics (Section 2.2, p. 49:5).
technique 2: Hindley generics and syntactic unification
What it is. A Hindley generic can occur negatively, under disjunction or negation, when it appears in a value-dependent position. At a call site, syntactic unification determines the function-valued predicate, so no higher-order quantifiers reach the SMT solver. The paper states that Hindley generic arguments can appear negatively âas long as they appear in a value-dependent positionâ (Section 2.2, p. 49:5).
How to apply it to query semantics. (1) Add a generic projection to Field<R,V>, such as proj: R -> V. (2) Give each generated field a concrete projection, e.g. |r| r.price. (3) Add an invariant generic to Query<R>. (4) Define eq, lt, and gt so their result predicate applies the field projection. (5) Define and, or, and not by composing subquery predicates, including negative occurrences. (6) Make run return only rows satisfying the query predicate. In Fig. 5, item_owner.eq(owner) gets |i| i.owner == owner, item_price.lt(cost) gets |i| i.price < cost, and and combines them; the loop assertion item.price < cost then follows (Section 2.2, pp. 49:6â7).
Verus form (Inference). Define a ghost query predicate spec fn eval(q, row) -> bool and represent query combinators as datatypes carrying proof-relevant predicates. Contracts on run can ensure every returned element satisfies eval. Since Verus does not infer higher-order ghost arguments this way, provide explicit predicate arguments and lemmas for and, or, and not; use requires/ensures on executable wrappers.
Evidence and limits. The system keeps SMT queries first-order and decidable by well-formedness invariants: refinement variables âare either base-sorted, or are functions that take base-sorted inputsâ, and refinement abstractions âare only used as generic arguments where they can be eliminated as part of constraint generationâ (Section 3.2, p. 49:12). A Hindley generic not used in a generic application in input position is ill-formed, e.g. a max that abstracts a predicate p over its inputs (Section 3.2, Example 2, p. 49:15). Inference: arbitrary higher-order predicates remain outside the supported fragment.
technique 3: associated generic refinements for traits
What it is. A trait can declare an associated predicate whose concrete definition is supplied by each implementation. For safe indexing, the trait declares reft in_bounds(v: Self, idx: Idx) -> bool and requires it in index; Vec<T> instantiates it as idx < v.len (Fig. 6, p. 49:9). This prevents a generic wrapper from bypassing an implementationâs safety precondition.
How to apply it. (1) Declare the associated refinement at the trait level over generic Self and index types. (2) Use it in the trait methodâs precondition. (3) Define the concrete predicate in every implementation. (4) Let rustcâs trait normalization instantiate the predicate at each call site. (5) Require generic wrappers to carry the trait predicate in their own preconditions. If only the Vec implâs index were refined, the wrapper get âwould typecheckâ and bypass the bounds check (Section 2.3, p. 49:8); with the associated generic, the uncorrected get does not typecheck, and the corrected wrapper takes i: usize{ C::in_bounds(c, i) } (Section 2.3, p. 49:9).
Verus form (Inference). Put a trait method contract on an ordinary Rust trait and define an associated proof relation, for example a spec fn taking &Self and an index. Each implementation supplies a lemma proving the relation implies its bounds. Generic wrappers require the relation explicitly. Verus trait specification syntax differs from Fluxâs reft; the invariant must be expressed with constructs documented for Verus.
Evidence and limits. The vector case refines the standard Vec rather than requiring a replacement RVec, avoiding a source-wide migration (Section 6.1, p. 49:21). Coverage is partial: the benchmarks use new, push, pop, len, index, and index_mut; pop_if is ânot currently supportedâ, and for dedup âwe cannot specify the exact length of a vector after a callâ (Section 6.1, p. 49:22).
technique 4: translate traits to dictionaries
The paper shows that associated generics reduce to Hindley generics through the classic typeclass-as-dictionary translation. (1) Translate a trait to a struct whose associated types become type parameters, methods become function-valued fields, and associated refinements become a Hindley generic of the struct. (2) Translate each implementation to an instance containing its concrete refinement. (3) Translate a trait-bound function to one with an explicit trait-struct parameter. (4) Pass the implementation instance at each call site (Section 2.4, pp. 49:9â10). The paper says this shows Hindley generics provide âa solid foundationâ for associated generics (Section 2.4, p. 49:9). Inference: it also gives a route to implement associated refinements in a language with ordinary higher-order ghost parameters.
Verus form (Inference). Encode the dictionary as a ghost trait object or a generic specification parameter with a method contract. Prove the concrete implementationâs predicate once, then pass its relation to generic functions. Executable dynamic dispatch and ghost specification dispatch must remain separated; Verus proof functions cannot be called at runtime.
technique 5: refined Diesel access control
RDiesel attaches an associated eval(e, r) refinement to every Diesel expression. Program values evaluate to themselves; fields evaluate to row projections; Add, Gt, Eq, and And compose the subexpression evaluations (Section 6.2.2, p. 49:24). select_where returns rows refined by Q::eval(qry, r). update_where requires forall row. Q::eval(qry, row) => F::allow_update(cx.user, row); insert_row requires R::allow_insert(cx.user, row) (Section 6.2.3, pp. 49:24â25).
How to apply it. (1) Define typed expression traits so only well-typed query compositions have Expr implementations (Section 6.2.1, p. 49:23). (2) Add an associated evaluation refinement to the expression trait. (3) Instantiate it for fields and program values. (4) Compose it structurally for every query constructor. (5) Make read methods return rows satisfying the refinement. (6) Add field and row policy refinements to update and insert preconditions. In Fig. 14, âif the code omitted the lines 8 and 9 then Flux would reject the codeâ, i.e. without the owner conjunct (Section 6.2.3, p. 49:26).
Verus form (Inference). Define a recursive ghost spec fn eval over a query enum and policy predicates over rows and users. Give select_where an ensures relation for each returned row, and require policy implications on update/insert. Prove each query constructorâs evaluation lemma by structural induction. This can establish policy preservation for the modeled query AST; it does not prove the database engine obeys the AST unless that boundary is separately trusted.
soundness, trust, and VL implications
Theorem 5.1 states soundness of the algorithmic (elaborating) system with respect to a declarative system; Theorem 5.2 states that the translation into FÏH is type preserving (Section 5, pp. 49:20â21). Full type soundness is only Conjecture 5.3, because Sekiyama et al. âonly conjectured, but did not prove, the upcast lemmaâ: âAssuming the upcast lemma holds, we can combine theorem 5.2 and the type soundness theorem of FÏH to conjecture soundness for λGâ (Section 5.3, p. 49:21). The algorithmic system âis fundamentally not complete because the Horn constraint solver is not completeâ (Section 5.1, p. 49:20). Inference: the trusted base includes the SMT solver, the Flux implementation, rustc trait normalization, the contract calculus, and the unproved upcast lemma. Inference: VL should treat generic contract instantiation as a useful design pattern, but should retain explicit proof obligations at trait boundaries rather than assume solver inference is complete.
For RDiesel the paper states: âWe ported some of the web applications from [17] to Rust using the RDiesel API and verified the same access control policiesâ (Section 6.2.4, p. 49:26); Fig. 13 reports only four application names with LOC, spec lines and time. Inference: the product threat is moderate. Generic predicates can reduce repeated annotations in agent-written Rust, while unrestricted higher-order or database semantics still require carefully trusted models and explicit lemmas.
techniques for the catalog
- Horn generic refinements: parameterize state invariants with positive predicates inferred by Horn constraints
- Hindley generic refinements: instantiate value-dependent predicates by syntactic unification, including negative occurrences
- associated trait refinements: define implementation-specific safety predicates at a generic trait boundary
- trait-to-dictionary translation: reduce associated predicates to explicit ghost function parameters
- refined query and policy APIs: compose query semantics and enforce row/field access policies in contracts Checked 2026-10-17 against the full paper by a Claude agent (mid-paper quote: âThis ensures that generic refinements, both Hindley and Horn, can be automatically instantiated at function calls.â): read all 29 pages of the text and checked every quotation, number, section and page reference, and technique step against it; changed the vector benchmark count from 8 to 7, fixed the
getwrapper claim (the uncorrected wrapper is rejected, not accepted, under the associated generic), fixed Fig. 2 code, the Theorem 5.1 description, several page references (Fig. 14 example to p. 49:26, Section 3.2 support to pp. 49:12/49:15), added supporting quotes, removed a repeated Fig. 13 paragraph, and marked trusted-base and other own conclusions âInference:â; did not check the technical appendix, the Flux artifact, or any Verus claims (all Verus forms are inference).
Last edited: