Contract Subtyping and Escaping Functions
Function values are only safe in verification when their behavior is summarized by contracts. This page explains how contract subtyping works and when contracts are required.
Why Contracts Matter for Function Values
When you pass a function as a value (higher-order) or store it in data structures, the verifier often cannot see the body at the call site. The only sound summary is the type contract:
- Prove
requiresbefore the call - Assume
ensuresafter the call - Apply
reads/modifiesfor framing
Without a contract, function values are treated as pure. Impure function values are rejected unless the callback type supplies a contract.
Subtyping Rules for Contracts
Contracts are checked by logical implication:
requiresis contravariantensuresis covariant
A function value can be used at a contract type when its precondition is no stronger than the expected precondition and its postcondition is at least as strong as the expected postcondition.
type Weak = (x : Nat) -> Nat contract {
requires x > 0;
ensures result >= x;
};
type Strong = (x : Nat) -> Nat contract {
requires x > 0;
ensures result > x;
};
let strong : Strong = ...;
let weak : Weak = strong; // OK
If the implication does not hold, the verifier rejects the coercion. This prevents callers from losing required input checks or assuming result facts the function does not guarantee.
Escaping Functions and $Self
When a function value can escape (returned, stored, or passed out), the verifier must know its effects on actor state.
- If a function may touch
$Self, its type contract must include fullreads/modifiesalong withrequires/ensures. trustedfunctions must still provide these contracts before they can escape as values.
This ensures callers get an explicit effect summary, even when the body is not verified.