call/cc variant of define/return

This commit is contained in:
2026-05-11 17:13:09 +02:00
parent 2fb61721f9
commit 0bbda2e6d6
2 changed files with 147 additions and 86 deletions
+48 -47
View File
@@ -1,7 +1,6 @@
#lang scribble/manual
@(require (for-label racket/base
racket/contract
"../main.rkt"))
@title{define/return}
@@ -9,41 +8,32 @@
@defmodule[define-return]
The @racketmodname[define-return] library provides definition forms with an
explicit early return. This 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 @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 early return is implemented with an internal exception. A @racket[return]
raises that exception, and the definition forms catch it around the function
body. The contracted form additionally checks early-returned values against
the result contract.
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.
See @racketmodname[define-return/contract] for the contracted version of
this module.
@section{Definitions with an early return}
@section{Return}
@defform[(define/return def return body ...)]{
@defform[(return val)]{
Like @racket[define], but @racket[body] may call @racket[return] to leave
the definition early.
Returns @racket[val] from the nearest dynamically enclosing
@racket[define/return] or @racket[define/contract/return] body.
The form is not a general escape continuation. It raises an internal return
exception. When used outside a body installed by this library, that exception
escapes.
}
@section{Uncontracted definitions}
@defform[(define/return def body ...)]{
Like @racket[define], but @racket[body] may use @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)
(define/return (status->symbol code) return
(unless (number? code)
(return 'not-a-number))
(when (= code 0) (return 'ok))
@@ -51,9 +41,7 @@ definition early.
(when (> code 5) (return 'out-of-range))
(cond
((= code 1) 'normal)
((>= code 2) (string->symbol (format "code-~a" (* code code))))
)
)
((>= code 2) (string->symbol (format "code-~a" (* code code))))))
]
The final expression is used when no early return is taken.
@@ -69,24 +57,37 @@ The final expression is used when no early return is taken.
}
@section{Contracted definitions}
@section{FFI-style checks}
See @racketmodname[define-return/contract].
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 small. It uses @racket[with-handlers] and an
internal exception type; it does not introduce prompts, continuations, or a
new calling convention.
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.
For @racket[define/contract/return], the normal function contract remains the
job of @racket[define/contract]. The extra returner only exists for the path
where @racket[return] leaves the body through an exception handler. That path
would otherwise bypass the ordinary result position of the function body.
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.
The contracted form does not try to parse arbitrary contract syntax. It
splits the inline contract form syntactically and reuses its last element as
the early-return result contract. This works well for ordinary result
contracts such as @racket[number?], @racket[symbol?],
@racket[(or/c symbol? number?)], and the result position of @racket[->*]
contracts.