Files
define-return/scrbl/define-return.scrbl
T
2026-05-11 17:13:09 +02:00

94 lines
3.1 KiB
Racket

#lang scribble/manual
@(require (for-label racket/base
"../main.rkt"))
@title{define/return}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@defmodule[define-return]
The @racketmodname[define-return] library provides @racket[define/return],
a small definition form with an explicit early return. It is useful in
compact defensive functions, and especially around FFI bindings, where a
null pointer, an error code, an unsupported state, or a failed precondition
should leave the function immediately.
The return mechanism is based on @racket[call/cc]. A @racket[define/return]
form captures the current continuation of 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.
@section{Definitions with an early return}
@defform[(define/return def return body ...)]{
Like @racket[define], but @racket[body] may call @racket[return] to leave
the definition early.
The @racket[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.
@racketblock[
(define/return (status->symbol code) return
(unless (number? code)
(return 'not-a-number))
(when (= code 0) (return 'ok))
(when (< code 0) (return 'failed))
(when (> code 5) (return 'out-of-range))
(cond
((= code 1) 'normal)
((>= code 2) (string->symbol (format "code-~a" (* code code))))))
]
The final expression is used when no early return is taken.
@codeblock|{
(status->symbol 0) ; => 'ok
(status->symbol -1) ; => 'failed
(status->symbol 10) ; => 'out-of-range
(status->symbol "Hi") ; => 'not-a-number
(status->symbol 1) ; => 'normal
(status->symbol 3) ; => 'code-9
}|
}
@section{FFI-style checks}
The form is deliberately small, but it fits the style of many C libraries.
For example, a wrapper can leave immediately when an FFI call returns a null
pointer:
@racketblock[
(define/return (make-codec-context codec-id) return
(define codec (avcodec-find-decoder codec-id))
(unless codec
(return 'decoder-not-found))
(define ctx (avcodec-alloc-context3 codec))
(unless ctx
(return 'alloc-codec-context-failed))
ctx)
]
The second call is evaluated only when the first one succeeded. If either
call returns @racket[#f], the function returns the corresponding symbol.
Otherwise the final expression returns the allocated context.
@section{Notes}
The mechanism is intentionally just a local escape. It uses @racket[call/cc]
to capture the continuation around the function body. The captured
continuation is bound to @racket[return] and can be called as a normal
procedure with one value.
Since @racket[return] is a continuation, not a syntax form, it can also be
passed to local helper procedures when that is useful. It should normally be
used only as an escape from the dynamic extent of the function body.