Structs and type invariants

Contracts may read fields of by-value (and reference) struct/class parameters with ordinary member syntax:

struct Rect { int w; int h; };

int area(Rect r)
  pre(r.w >= 0 && r.w <= 1000 && r.h >= 0 && r.h <= 1000)
  post(result >= 0)
{ return r.w * r.h; }

type_invariant

A type_invariant states a property every instance of a type maintains. Declare it after the fields it names (it is parsed in place):

struct Point {
  int x;
  int y;
  type_invariant(x >= 0 && x <= 1000 && y >= 0 && y <= 1000);
};

int sum(Point p)
  post(result >= 0 && result <= 2000)
{ return p.x + p.y; }            // x, y in [0, 1000] are assumed from the invariant

Semantics:

  • The invariant is injected as a precondition at the first use of an invariant field, for by-value and reference (Point, const Point&) parameters. Because it is a precondition, callers must establish it when they pass such a value.

  • It is not injected for raw pointer-to-struct parameters (their fields are heap loads).

  • Returns are checked: when a function returns a struct value of an invariant-bearing type, the verifier asserts the invariant holds for the returned value. Building and returning a struct whose fields violate the invariant is a verification error.

struct Box { int w; int h; type_invariant(w >= 0 && h >= 0); };

Box make(int a, int b)
  pre(a >= 0 && a <= 50 && b >= 0 && b <= 50)   // drop b >= 0 and this fails
{ Box x; x.w = a; x.h = b; return x; }          // invariant asserted here

The invariant may be temporarily broken inside the function while you assign fields one at a time — it is only required to hold at the return (the encapsulation boundary), not after each individual field write.

View functions

Define spec functions that expose a mathematical view of a struct, then write contracts against the view rather than the layout:

struct Arr { int data[100]; int len; };

spec int size(Arr a) { return a.len; }