let/assert
| (require let-assert) | package: let-assert |
This module provides let/assert, a small sequential binding form with local assertions. It is useful for defensive programming around FFI bindings: checks for null pointers, exit codes, and similar failure values can be kept close to the binding that produced them, while the body stays on the happy path. When an assertion fails, the whole let/assert expression returns the associated fallback value.
1 Binding with assertions
syntax
(let/assert (binding ...) body ...+)
binding = [id expr] | [id expr predicate-expr fallback-expr]
The implementation expands through an internal helper into nested let and cond forms. There is no exception-based control flow in this version: a failing assertion directly selects the fallback result for that binding.
A binding of the form [id expr] simply binds id to expr.
A binding of the form [id expr predicate-expr fallback-expr] evaluates expr once, binds the result to id, and then calls predicate-expr with that value. If the predicate returns a true value, evaluation continues with the next binding or with the body. If the predicate returns #f, the body is not evaluated and the complete let/assert form returns fallback-expr.
The fallback expression belongs to the binding where the assertion is made. This keeps failure handling close to the operation that may fail.
(let/assert ([x 10 (a->? 0) 'too-small] [y (+ x 2) (a-=? 12) 'wrong-value]) y)
This expression returns 12. The second binding may use x, because the bindings are evaluated sequentially.
(let/assert ([ptr (open-native-handle) a-!nullptr? 'open-failed] [ret (use-native-handle ptr) (a->=? 0) 'call-failed]) 'ok)
This style is useful around FFI code. If open-native-handle returns #f, the result is 'open-failed. If use-native-handle returns a negative status code, the result is 'call-failed. Otherwise the body is evaluated and the result is 'ok.
2 FFI-style example
Many C libraries, including FFmpeg-style APIs, report failure through null pointers or integer return codes. A typical wrapper can therefore keep the normal path small:
(define (open-decoder file) (let/assert ([ctx (avformat-open-input file) a-!nullptr? 'open-input-failed] [ret (avformat-find-stream-info ctx) (a->=? 0) 'stream-info-failed] [stream (find-best-audio-stream ctx) a-!nullptr? 'no-audio-stream]) stream))
The example is intentionally written as wrapper-style Racket code rather than as a direct FFmpeg binding. The important point is the shape: pointer-returning operations are checked with a-!nullptr?, while return-code operations are checked with predicates such as (a->=? 0).
3 Creating assertion factories
syntax
(make-assert name not-name pred)
The form (name constant) produces a unary predicate that applies pred to the checked value and constant:
(lambda (x) (pred x constant))
The form (not-name constant) produces the negated variant:
For example:
4 Assertion factories
syntax
(a-eq? constant)
syntax
(a-!eq? constant)
syntax
(a->? constant)
syntax
(a-<=? constant)
syntax
(a->=? constant)
syntax
(a-<? constant)
syntax
(a-=? constant)
syntax
(a-!=? constant)
The negated factories are exact negations of their corresponding predicate. For ordinary numeric values, (a-<=? n) behaves like a less-than-or-equal check and (a-<? n) behaves like a less-than check, but they are implemented as negations of > and >=.
5 Ready-made predicates
procedure
(a-nullptr? x) → boolean?
x : any/c
procedure
(a-!nullptr? x) → boolean?
x : any/c