Status: Not yet implemented -- requires
-Xcontractsflag (planned for v4).For runtime contract macros available today (
assert!,require!,ensure!,invariant!), see Error Handling Guide.
Contract types add runtime-checked predicates to types as a first-class language feature.
A contract type { x : T | p } represents values of type T that satisfy predicate p,
verified automatically at function boundaries and assignment sites.
The existing assert!, require!, and ensure! macros in stdlib/contract.tur are
imperative guards -- you place them manually inside function bodies. Contract types are
declarative annotations: you write the predicate in the type, and the compiler inserts
the checks at every crossing point.
| Approach | Where you write it | When it runs |
|---|---|---|
require! macro (today) |
Inside the function body | When execution reaches that line |
Contract type :pre (CT1) |
On the defn declaration |
Automatically at every call site |
Inline contract { x : T \| p } (CT1) |
In the parameter or return type | At every use of that type |
Contract predicates are ordinary Turmeric expressions -- no special syntax beyond the
{ x : T | p } wrapper. They are pure by default; the elaborator enforces #{} on
all predicate expressions.
;; A non-negative integer
(deftype Nat { x : int | (>= x 0) })
;; Inline in a function signature
(defn sqrt [x : { y : double | (>= y 0) }] : double
...)
The bound variable (x, y) is local to the predicate expression. It refers to the
value being checked, not to any name in the surrounding scope.
(defn divide [x : int, y : int] : int
:pre (!= y 0)
:post (= (* result y) x)
(/ x y))
:pre -- checked at function entry, before any user code runs.:post -- checked before each return; the return value is bound to result inside
the predicate expression.defn(defn safe-sqrt [x : double] : double
:pre (>= x 0.0)
:post (>= result 0.0)
(sqrt x))
(defstruct BoundedBuffer [
data : (vec int)
index : { i : int | (and (>= i 0) (< i (vec/len data))) }])
The predicate for a struct field may reference other fields in scope at elaboration time.
(extern-c sqlite3_column_int [stmt : ptr, col : int] : int
:pre (and (!= stmt null) (>= col 0))
:post (>= result 0))
The contract documents and enforces the requirement at the call site. Inside the body, no redundant check is needed:
(defn vec-get-checked [v : (vec a), i : { x : int | (and (>= x 0) (< x (vec/len v))) }] : a
(vec/get-unsafe v i))
;; At the call site, the elaborator inserts:
;; assert((3 >= 0) && (3 < vec_len(v)), "contract violated: i in bounds")
;; vec_get_unsafe(v, 3)
(defn example [v : (vec int)] : int
(vec-get-checked v 3))
Accept an untyped value and narrow it at runtime:
(defn ensure-positive [x : any] : { y : int | (>= y 0) }
:contract (>= x 0)
x)
(defn pipeline [] : { y : int | (>= y 0) }
(ensure-positive 42))
Contracts are checked before the C call and on return:
(extern-c sqlite3_column_int [stmt : ptr, col : int] : int
:pre (and (!= stmt null) (>= col 0))
:post (>= result 0))
(defn safe-sqrt [x : double] : double
:pre (>= x 0.0)
:post (>= result 0.0)
(sqrt x))
| Site | When checked |
|---|---|
| Function entry | :pre predicates; { x : T \| p } on parameters |
| Function return | :post predicates; { x : T \| p } on return type |
| Let binding | { x : T \| p } on the bound variable's type |
extern-c call |
:pre before call; :post on return value |
By default, a violated contract calls panic. A custom handler can be registered:
(set-contract-handler! (fn [msg location] : unit
(log/error (str "Contract violated at " location ": " msg))))
For scoped overrides:
(with-contract-handler my-handler
(do-something-with-contracts))
Enable contract types with the -Xcontracts flag:
turc -Xcontracts myfile.tur
| Build | Contracts |
|---|---|
just build (debug) |
Always active, regardless of -Xcontracts |
just release |
Stripped by default |
just release with --keep-contracts |
Retained (safety-critical code) |
Contracts are compile-time insertions. A release build with no flag produces the same binary as code written without contract annotations.
| Code | Message |
|---|---|
TUR_E0400 |
Contract violated: {predicate} is false for value {value} |
TUR_E0401 |
Postcondition violated: {predicate} is false for result {value} |
Run tur explain TUR_E0400 for full diagnostic output with source location and predicate text.
| Feature | Notes |
|---|---|
stdlib/contract.tur (assert!, require!, ensure!) |
Unchanged -- macros are imperative guards; contract types are declarative annotations |
| Borrow checker | Orthogonal -- contracts are runtime; borrow checking is compile-time |
| Typeclasses | Predicates can call typeclass methods; predicate is an ordinary Turmeric expression |
| Algebraic effects | Predicates are pure (#{} enforced); effects inside predicates are not permitted |
any type |
Contract types enable gradual typing for any values |
FFI (extern-c) |
:pre / :post validated at the Turmeric/C boundary |
Use contract macros when: - Adding a one-off sanity check inside a function body - The check is only relevant in a specific branch, not at all entry points - You want imperative control over exactly where the check runs
Use contract types when: - The predicate is part of the type's meaning (e.g. "a non-negative integer") - You want the compiler to enforce the check at every call site automatically - Writing a library API where callers should be told the invariant upfront - Validating at FFI boundaries
Contract types are planned in four phases:
| Phase | Goal |
|---|---|
| CT0 | Parse { x : T \| p } syntax; TY_CONTRACT type node; store :pre/:post/:contract on FnDef |
| CT1 | Insert runtime checks at function entry, exit, let bindings, and extern-c boundaries |
| CT2 | Propagate and simplify contracts through operations; subtype relation { x : T \| p } <: T |
| CT3 | Eliminate provably-true checks at compile time; debug/release build switch |
| CT4 | FFI contracts; (cast x : T) downcast from any; contract handler API; tur explain entries |
Result, Option, panic, and today's contract macrosextern-c