define/contract/return
| (require define-return/contract) |
The define-return/contract library provides define/contract/return, a contracted definition form with an explicit early return. It is useful in small defensive functions, and especially around FFI bindings, where null pointers, error codes, unsupported states, or failed preconditions should leave the function immediately.
The return mechanism is based on call/cc. A function definition captures the continuation around the function body and binds it to the return identifier supplied by the programmer. Calling that identifier leaves the body and returns the supplied value as the result of the function. No exception is raised and no exception handler is installed.
The module re-exports racket/contract, so contracts such as ->, ->*, any/c, or/c, and/c, and listof are available from the same require. It also re-exports define-return.
Note. This can lead to a clash between -> from racket/contract and -> from ffi/unsafe.
1 Contracted definitions
syntax
(define/contract/return (name . formals) return-id (contract-part ... result-contract) body ...)
(define/contract/return name result-contract value)
The first form defines a contracted function. The return-id identifier is part of the surface syntax. It is not a predefined binding exported by this module. This keeps the form hygienic: the programmer chooses the name of the local escape continuation, and the body uses that same name explicitly.
Ordinary results are checked by define/contract. Early-returned values are checked separately against result-contract. The implementation does this by defining a small local contracted returner with contract (-> any/c result-contract). The early return value therefore passes through Racket’s contract machinery before it leaves the function body.
The contract must be written inline as a parenthesized contract form. The last element is used as the early-return result contract.
(define/contract/return (h x) return (-> number? (or/c symbol? number?)) (when (< x 0) (return 'x-not-positive)) (let ((y (* x x))) (when (> y 100) (return 'maxed-out)) (when (= y 9) (return "Wrong answer!")) y))
Here the result contract is (or/c symbol? number?). The symbol returns are accepted, ordinary numeric results are accepted, and the string "Wrong answer!" is rejected.
(h -1) ; => 'x-not-positive (h 2) ; => 4 (h 11) ; => 'maxed-out (h 3) ; contract violation: string result
A zero-argument function works in the same way:
(define/contract/return (z) return (-> symbol?) (let ((cs (current-seconds))) (when (= (remainder cs 3) 0) (return "divisible by 3")) (when (= (remainder cs 2) 0) (return 'divisible-by-2)) 'yes))
The result contract is symbol?. Returning 'divisible-by-2 or 'yes is accepted. Returning the string "divisible by 3" is rejected.
Rest arguments can be used when the corresponding contract form is accepted by define/contract:
(define/contract/return (sum . xs) return (->* () #:rest (listof number?) number?) (when (null? xs) (return 0)) (apply + xs))
(sum) ; => 0 (sum 1 2) ; => 3 (sum 1 'x) ; contract violation
Since return-id is a continuation wrapped in a local contracted procedure, it is called with one value. The continuation should normally be used only to escape from the dynamic extent of the function body.
The second form defines a contracted value. The value is checked against result-contract. This form has no return-id position, so it does not expose a local early-return identifier. Use the function form when a body needs an explicit early return.
(define/contract/return answer number? 42)
This definition succeeds because 42 satisfies number?.
(define/contract/return bad-answer number? 'not-a-number)
This definition raises a contract violation, because 'not-a-number does not satisfy number?.
2 FFI-style null pointers
Many FFI bindings represent a native null pointer as #f. The explicit return identifier makes it easy to leave a wrapper as soon as a required pointer is missing, while still checking the final result against the function contract.
(define/contract/return (make-stream-codec-context stream) return (-> cpointer? (or/c cpointer? symbol?)) (define codecpar (AVStream-codecpar stream)) (unless codecpar (return 'missing-codec-parameters)) (define codec (avcodec-find-decoder (AVCodecParameters-codec-id codecpar))) (unless codec (return 'decoder-not-found)) (define ctx (avcodec-alloc-context3 codec)) (unless ctx (return 'alloc-codec-context-failed)) ctx)
The checks are sequential. The decoder lookup is evaluated only when codecpar is a valid pointer, and the context allocation is evaluated only when codec is a valid pointer. A failed pointer check returns the corresponding symbol immediately. A successful path returns ctx, and both the ordinary result and early-returned symbols are covered by the result contract.