Laurel User Guide

4.8. Procedures, outputs, and calls🔗

A procedure has three output styles. It may have none, one anonymous output, or named outputs:

procedure log(x: int) { assert x == x };

procedure addOne(x: int): int
{
  return x + 1
};

procedure quotientAndRemainder(a: int, b: int)
  returns (q: int, r: int)
  requires b != 0
  opaque
  ensures a == q * b + r
{
  q := a / b;
  r := a % b
};

The short : T form names the output $result, which is the one exception to the reserved leading $ (see Reserved names): you may write returns ($result: T) explicitly and refer to it in a contract. Prefer returns (r: T) with a name of your own whenever a contract needs the result. return e is valid only when there is exactly one output; a multi-output procedure assigns its named outputs and uses a bare return for an early exit, which leaves the outputs at whatever they were last assigned.

An input and an output that share a name form an inout parameter — the standard way to model a procedure that updates its argument:

procedure bump(x: int) returns (x: int)
  opaque
  ensures x == old(x) + 1
{
  x := x + 1
};

Top-level procedures may share a name when their parameter signatures do not overlap; resolution picks the unique overload the argument types accept, and both no match and several matches are errors. external procedures cannot be overloaded.

4.8.1. Bodiless and external procedures🔗

A procedure may be declared without a body, in which case its contract is all there is. This is the right way to model an operation whose implementation is outside the program — a boundary call, or a stub standing in for code not yet translated. Its outputs are arbitrary subject to its postconditions:

procedure boundary(x: int) returns (r: int)
  opaque
  ensures r >= x;

external is different: it declares an operation supplied by the Core environment itself. The declaration exists so Laurel can resolve calls to it, and is then dropped, with each call becoming an application of a Core operator. Use exactly one output.

procedure hostPrimitive(x: int): int external;

Do not reach for external when what you want is an unconstrained value — that is a bodiless opaque procedure. The distinction between a transparent procedure, whose body callers may reason through, and an opaque one, whose contract is all callers see, is a verification concern and is covered under Postconditions.