#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.