# Let-assert & define-return

**URL:** <https://racket.discourse.group/t/let-assert-define-return/4208>\
**Category:** Show & Tell\
**Created:** [May 11, 2026, 9:58am UTC](https://racket.discourse.group/t/let-assert-define-return/4208 "2026-05-11T09:58:28Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![hnmdijkema](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hnmdijkema/32/2629_2.png) [@hnmdijkema](https://racket.discourse.group/u/hnmdijkema)\
**Post date:** [May 11, 2026, 9:58am UTC](https://racket.discourse.group/t/let-assert-define-return/4208/1 "2026-05-11T09:58:28Z")

</div>

I just released two small modules:

## let-assert (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](https://docs.racket-lang.org/let-assert/index.html#%28form._%28%28lib._let-assert%2Fmain..rkt%29._let%2Fassert%29%29) expression returns the associated fallback value.

Example of use in racket-audio (under development):

```scheme
(define (decode-next! ds)
  (let/assert ([pkt (av-packet-alloc) a-!nullptr? 'packet-allocation-failed]
               [ret (read-selected-audio-packet! ds pkt)
                    (a->=? 0)
                    'read-packet-failed]
               [pcm (receive-available-frames! ds)
                    bytes?
                    'decode-failed])
    pcm))

```

## define-return (define/return, define/contract/return)

The [define-return](https://docs.racket-lang.org/define-return@define-return/index.html) 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.

A small example, which does nothing special:

```scheme
(define/return (status->symbol code)
  (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))))
    )
  )

```

---

<div class="post-metadata">

**Author:** ![damien\_mattei](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/damien_mattei/32/2706_2.png) [@damien\_mattei](https://racket.discourse.group/u/damien_mattei)\
**Post date:** [May 11, 2026, 1:07pm UTC](https://racket.discourse.group/t/let-assert-define-return/4208/2 "2026-05-11T13:07:58Z")

</div>

Interesting. Your implementation seems to use special Racket features.  
In Scheme+ for Racket there is an implementation using scheme only. (see [doc](https://docs.racket-lang.org/Scheme-PLUS-for-Racket/index.html#%28form._%28%28lib._.S.R.F.I-110%2Fmain..rkt%29._def%29%29) and [code](https://github.com/damien-mattei/Scheme-PLUS-for-Racket/blob/main/def.rkt#L66) and [optionally this](https://github.com/damien-mattei/Scheme-PLUS-for-Racket/blob/main/return.rkt) code)

---

<div class="post-metadata">

**Author:** ![hnmdijkema](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hnmdijkema/32/2629_2.png) [@hnmdijkema](https://racket.discourse.group/u/hnmdijkema)\
**Post date:** [May 11, 2026, 1:29pm UTC](https://racket.discourse.group/t/let-assert-define-return/4208/3 "2026-05-11T13:29:53Z")

</div>

Interesting 😃. Without those racket-specific designs, however, it’s a lot harder to implement.

---

<div class="post-metadata">

**Author:** ![hnmdijkema](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hnmdijkema/32/2629_2.png) [@hnmdijkema](https://racket.discourse.group/u/hnmdijkema)\
**Post date:** [May 11, 2026, 2:09pm UTC](https://racket.discourse.group/t/let-assert-define-return/4208/4 "2026-05-11T14:09:31Z")

</div>

With only R5RS (no syntax-case), it could be implemented as follows.

```scheme
(define-syntax define/return
  (syntax-rules ()
    ((_ (name . args) return b1 ...) 
     (define (name . args)
       (call/cc
        (lambda (return)
          b1 ...))))))

(define/return (f x) return
   (cond 
     ((not (number? x)) (return 'not-a-number))
     ((< x 10) (return 'to-small))
     )
   (* x x))

```

However, this would be about the same as [let/ec](https://docs.racket-lang.org/reference/cont.html#%28form._%28%28lib._racket%2Fprivate%2Fletstx-scheme..rkt%29._let%2Fec%29%29).

Although my variant goes one step further because the exception also captures this:

```scheme
> (require define-return)

> (define/return (g x)
    (let ((h (f x)))
      (+ h 1000)))

> (define (f x)
       (cond 
     ((not (number? x)) (return 'not-a-number))
     ((< x 10) (return 'to-small))
     )
   (* x x))

> (g 10)
1100
> (g 8)
'to-small
> (g 'd)
'not-a-number
>

```

Which is probably less intuïtive by the way...

---

<div class="post-metadata">

**Author:** ![hnmdijkema](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hnmdijkema/32/2629_2.png) [@hnmdijkema](https://racket.discourse.group/u/hnmdijkema)\
**Post date:** [May 11, 2026, 2:47pm UTC](https://racket.discourse.group/t/let-assert-define-return/4208/5 "2026-05-11T14:47:48Z")

</div>

This makes me reconsider my implementation. I'll probably make it more intuïtive if I choose the call/cc, which then will be more syntactic sugar around let/ec etc.

---

<div class="post-metadata">

**Author:** ![damien\_mattei](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/damien_mattei/32/2706_2.png) [@damien\_mattei](https://racket.discourse.group/u/damien_mattei)\
**Post date:** [May 11, 2026, 10:27pm UTC](https://racket.discourse.group/t/let-assert-define-return/4208/6 "2026-05-11T22:27:07Z")

</div>

Honestly the implementation was not of mine,and now with some IA i have this :

```scheme
#lang racket

;; dynamic parameters
(define current-return (make-parameter #f)) ; local return
(define current-return-rec (make-parameter #f)) ; global return

;; local return (same as before)
(define (return . v)
  (let ([ret (current-return)])
    (unless ret
      (error "return used outside of def"))
    (ret (if (null? v) (void) (car v)))))

;; global return (exits all recursion)
(define (return-rec . v)
  (let ([ret (current-return-rec)])
    (unless ret
      (error "return-rec used outside of def"))
    (ret (if (null? v) (values) (car v))))) ; (values) or (void)

;; def macro (UPDATED)
(define-syntax-rule (def (name . args) body ...)
  (define (name . args)
    (if (current-return-rec)
        ;; recursive call
        (call/cc
         (lambda (local-ret)
           (parameterize ([current-return local-ret])
             body ...)))
        ;; root call
        (call/cc
         (lambda (global-ret)
           (parameterize ([current-return-rec global-ret])
             (call/cc
              (lambda (local-ret)
                (parameterize ([current-return local-ret])
                  body ...)))))))))

(def (f x)
  (display "start\n")
  (when (> x 0)
      (return "positive"))
  (display "after if\n")
  "end")

(def (bar n)
  (if (= n 0)
      (return-rec "finish")
      (bar (- n 1)))
  "should never be here")

(def (foo n)
  (if (= n 0)
      (return "finish")
      (foo (- n 1)))
  "should also be here unless n=0")

(def (g a . L) (when #t (return (cons a L))))

(bar 7)

(foo 7)

(g 3 4 5)

```

it implements also a return-rec ,escaping of any recursive calls.

It's amazing but IA is just a reformulation of things already existing on forums.

```scheme
Welcome to DrRacket, version 8.18 [cs].
Language: racket, with debugging; memory limit: 128 MB.
"finish"
"should also be here unless n=0"
'(3 4 5)

```

but perheaps i will update the previous implementation in my project with this one... (but. note that make-parameter is not very portable)

---

<div class="post-metadata">

**Author:** ![hnmdijkema](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hnmdijkema/32/2629_2.png) [@hnmdijkema](https://racket.discourse.group/u/hnmdijkema)\
**Post date:** [May 12, 2026, 12:08am UTC](https://racket.discourse.group/t/let-assert-define-return/4208/7 "2026-05-12T00:08:45Z")

</div>

I'm currently working on an 'early-return' syntax-definition, which is more to the point on my use case:

```scheme
(define-syntax early-return*
  (syntax-rules (-> ? ~ do)
    ((_ () (b1 ...))
     (let () b1 ...))
    ((_ ((do d1 ...)) (b1 ...))
     (let () d1 ...
       (let () b1 ...)))
    ((_ ((do d1 ...) c1 ...) (b1 ...))
     (let () d1 ...
       (early-return* (c1 ...) (b1 ...))))
    ((_ ((v expr ? pred? -> retval ~ cleanup)) (b1 ...))
     (let ((v expr))
       (cond (pred? cleanup retval)
             (else (let () b1 ...)))))
    ((_ ((v expr ? pred? -> retval ~ cleanup) c1 ...) (b1 ...))
     (let ((v expr))
       (cond (pred? cleanup retval)
             (else (early-return* (c1 ...) (b1 ...))))))
    ((_ ((v expr ? pred? -> retval)) (b1 ...))
     (let ((v expr))
       (cond (pred? retval)
             (else (let () b1 ...)))))
    ((_ ((v expr ? pred? -> retval) c1 ...) (b1 ...))
     (let ((v expr))
       (cond (pred? retval)
             (else (early-return* (c1 ...) (b1 ...))))))
    ((_ ((? pred? -> retval)) (b1 ...))
     (cond (pred? retval)
           (else (let () b1 ...))))
    ((_ ((? pred? -> retval) c1 ...) (b1 ...))
     (cond (pred? retval)
           (else (early-return* (c1 ...) (b1 ...)))))
    ((_ ((? pred? -> retval ~ cleanup)) (b1 ...))
     (cond (pred? cleanup retval)
           (else (let () b1 ...))))
    ((_ ((? pred? -> retval ~ cleanup) c1 ...) (b1 ...))
     (cond (pred? cleanup retval)
           (else (early-return* (c1 ...) (b1 ...)))))
    ((_ ((v expr)) (b1 ...))
     (let ((v expr))
       b1 ...))
    ((_ ((v expr) c1 ...) (b1 ...))
     (let ((v expr))
       (early-return* (c1 ...) (b1 ...))))
    )
  )

(define-syntax early-return
  (syntax-rules ()
    ((_ (er1 ...) b1 ...)
     (early-return* (er1 ...) (b1 ...))
     )
    )
  )

```

Now, I've been able to rewrite e.g. a function in my ffmpeg-definition.rkt.

```scheme
(define (drain-resampler! self)
  (let* ((dec (fmpg-instance-decoder self))
         (info (fmpg-instance-audio-info self))
         (channels (ais-channels info))
         (sample-rate (ais-rate info))
         (continue (gensym 'continue)))

    (define (drain-once! delay max-bytes produced)
      (early-return
       ((tmp (malloc max-bytes 'raw) ? (eq? tmp #f) -> -1)
        (out-planes (malloc _pointer 1 'raw) 
                    ? (eq? out-planes #f) -> -1 ~ (free tmp))
        (cleanup! (λ ()
                   (unless (eq? out-planes #f) (free out-planes))
                   (unless (eq? tmp #f) (free tmp))))
        (do (ptr-set! out-planes _pointer 0 tmp))
        (out-samples (swr_convert (ds-swr-ctx dec) out-planes delay #f 0)
                     ? (<= out-samples 0) -> produced
                     ~ (cleanup!))
        (used-bytes (av_samples_get_buffer_size #f channels out-samples
                                                FMPG_OUTPUT_FMT 1)
                    ? (< used-bytes 0) -> produced
                    ~ (cleanup!))
        (do
         (when (pcm-empty? dec)
           (ds-start-sample! dec (ds-next-sample-pos dec))
           (ds-timecode! dec (/ (exact->inexact (ds-start-sample dec))
                                (exact->inexact sample-rate)))))
        (appended? (append-bytes! dec tmp used-bytes)  
                    ? (not appended?) -> -1 ~ (cleanup!))
        (do
         (ds-last-samples! dec (+ (ds-last-samples dec) out-samples))
         (ds-next-sample-pos! dec (+ (ds-next-sample-pos dec) out-samples))
         (cleanup!)))

       continue))

    (let loop ((produced 0))
      (early-return
       ((delay (swr_get_delay (ds-swr-ctx dec) sample-rate)
               ? (<= delay 0) -> produced)
        (max-bytes (av_samples_get_buffer_size #f channels delay FMPG_OUTPUT_FMT 1)
                   ? (<= max-bytes 0) -> produced)
        (r (drain-once! delay max-bytes produced)
           ? (not (eq? r continue)) -> r))
       (loop 1)))
    )
  )

```

No `call/cc` nor exceptions used anymore. I didn't like the idea of the call/cc overhead.

Simple dumb example:

```scheme
> (define (h x)
    (early-return
     ((? (not (number? x)) -> 'not-a-number ~ (displayln (format "cleaning up with (h 8): ~a" (h 8))))
      (z (+ x x))
      (do (displayln (format "z = ~a" z)))
      (v (* x x) ? (> v 100) -> 'too-big)
      (do (displayln (format "v = ~a" v)))
      (do (define (f x) (/ x 3)))
      (g (+ v 10) ? (< g 25) -> 'too-small)
      (do (displayln (format "g = ~a" g)))
      )
     (f (+ g v 100 z))))
> (h 34)
z = 68
'too-big
> (h 3)
z = 6
v = 9
'too-small
> (h 8)
z = 16
v = 64
g = 74
84 2/3
> (h 'no-num)
z = 16
v = 64
g = 74
cleaning up with (h 8): 254/3
'not-a-number
>

```

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [May 12, 2026, 8:13am UTC](https://racket.discourse.group/t/let-assert-define-return/4208/8 "2026-05-12T08:13:00Z")

</div>

> [@hnmdijkema](#):
>
> No `call/cc` nor exceptions used anymore. I didn't like the idea of the call/cc overhead.

You might be interested in the [`guard`](https://docs.racket-lang.org/guard/index.html) library by @notjack. It is also implemented without continuation-related features.

If you _did_ use continuation-related features, `call-with-continuation-prompt`/`abort-current-continuation` could be an alternative to `let/ec`. However …

> [@hnmdijkema](#):
>
> ```scheme
> ((tmp (malloc max-bytes 'raw) ? (eq? tmp #f) -> -1)
> (out-planes (malloc _pointer 1 'raw) 
> ? (eq? out-planes #f) -> -1 ~ (free tmp))
> (cleanup! (λ ()
> (unless (eq? out-planes #f) (free out-planes))
> (unless (eq? tmp #f) (free tmp))))
> 
> ```

You can avoid much of the need for early return by writing more idiomatic Racket.

Unlike the C function of the same name, `malloc` will only return `#f` if you have requested an an allocation of size zero. Out-of-memory errors raise an `exn:fail:out-of-memory`, instead.

Furthermore, you won't need `cleanup!` if you avoid allocating in `'raw` mode: either `'atomic-interior` or (IIUC in your case) `'atomic` mode will let the memory be GCed normally.

You might be able to go further: a Racket byte string can be used directly as a `cpointer?`, and I'm not sure if `out-planes` really needs to be allocated separately from `tmp` (but maybe I just haven't followed closely enough).

Since much of this is a loop, you might also try to fit it into `for/fold`, which comes with features like `#:break` if needed.

---

<div class="post-metadata">

**Author:** ![hnmdijkema](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hnmdijkema/32/2629_2.png) [@hnmdijkema](https://racket.discourse.group/u/hnmdijkema)\
**Post date:** [May 12, 2026, 8:37am UTC](https://racket.discourse.group/t/let-assert-define-return/4208/9 "2026-05-12T08:37:59Z")

</div>

Interesting, thanks. Couple of remarks/questions.

- I wasn't aware of the out-of-memory exn, thought malloc was more low level, following C-semantics.
- I'm currently using malloc 'raw / free, because I know that this memory can be deleted and I suppose this could be more efficient than letting the GC handle it. As this is part of an audio pipeline, I would want is to be as efficient as possible. Is my assumption correct?
- I'll look into for/fold.
- Do you have an example of 'idomatic racket'? I'm using early returns, because this checks boxes and leaves me with in a checked state for the 'good path'.

---

<div class="post-metadata">

**Author:** ![LiberalArtist](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/liberalartist/32/151_2.png) [@LiberalArtist](https://racket.discourse.group/u/LiberalArtist)\
**Post date:** [May 19, 2026, 1:32am UTC](https://racket.discourse.group/t/let-assert-define-return/4208/10 "2026-05-19T01:32:10Z")

</div>

> [@hnmdijkema](#):
>
> I'm currently using malloc 'raw / free, because I know that this memory can be deleted and I suppose this could be more efficient than letting the GC handle it. As this is part of an audio pipeline, I would want is to be as efficient as possible. Is my assumption correct?

With the caveat that everything is relative and that there’s no substitute for benchmarking: I would expect using `'raw` mode would be _less_ efficient than using a `malloc` mode that cooperates with the GC.

When you call Racket’s `malloc` in `'raw` mode, Racket has to really call the C `malloc()` function, because the semantics of `'raw` mode allow ownership to be transferred to a foreign library that will `free()` it from C later. Typical C `malloc()` implementations (there is a whole world of alternative C `malloc()`s …) are not tuned to make small, short-lived allocations efficiently. This is why C programmers typically avoid heap allocations when possible in favor of stack allocation, especially when performance is a concern. (Racket will also incur a little extra overhead from having to switch to C calling conventions, but this is small and improving: I wouldn’t worry about it.)

In contrast, Chez Scheme’s “automatic storage management system” _is_ tuned to make small, short-lived allocations efficiently. (This discussion is an example of why I’ve come to appreciate Dybvig et al.’s use of “automatic storage management system” when they are talking about more than the GC per se: the allocator matters as much as the collector.) In the optimal case, it will simply increment an allocation pointer, which its custom calling convention even keeps in a machine register on most architectures. If you need to use `'atomic-interior` mode (i.e. if the memory must not be moved), there would be some more overhead, but I’d expect it to be more efficient than C `malloc()`. And reclaiming space from a short-lived allocation just means that it isn’t copied out of the nursery.

My rule of thumb would be to only use `'raw` mode when you specifically need to interoperate with `free()` from C.

Allocation does have costs, and allocating less will typically perform better than allocating more, but cooperating with the automatic storage management system is usually a good idea.

Something else to think about is that, if your whole `drain-resampler!` function isn’t running in “uninterruptable mode” (or stronger), it might be killed without ever running `cleanup!`. The [`ffi/unsafe/alloc`](https://docs.racket-lang.org/foreign/Allocation_and_Finalization.html) library provides abstractions that manage finalization, typically used in conjustion with [`ffi/unsafe/define`](https://docs.racket-lang.org/foreign/Defining_Bindings.html) for libraries that provide functions like `make_foo()`/`free_foo()`. You could use the same tools to abstract over `'raw` mode, but that would introduce even more overhead compared to just using `'atomic` mode.

* * *

> [@hnmdijkema](#):
>
> Do you have an example of 'idomatic racket'? I'm using early returns, because this checks boxes and leaves me with in a checked state for the 'good path'.

Examples are hard: I think it’s useful to see something at a more realistic scale than a tutorial, but real projects may be _very_ big, or may have to design around legacy constraints that wouldn't apply to new construction. Two examples I often look to are the implementation of the [`db`](https://docs.racket-lang.org/db/index.html) library’s SQLite backend in [racket/racket/collects/db/private/sqlite3 at master · racket/racket · GitHub](https://github.com/racket/racket/tree/master/racket/collects/db/private/sqlite3) and the implementation of `racket/draw` in [draw/draw-lib/racket/draw/unsafe at master · racket/draw · GitHub](https://github.com/racket/draw/tree/master/draw-lib/racket/draw/unsafe) (mostly).

A coincidental quirk is that they both use the `racket/class` object system extensively. The `db` implementation is IMO a very elegant use: the `db` library doesn’t expose an object-oriented interface to users, but it uses object orientation internally to help with supporting multiple backends. (For example, the `#:use-place` argument works the same way for `sqlite3-connect` and `odbc-connect`, both being [FFI-based rather than wire-based connections](https://docs.racket-lang.org/db/notes.html#%28part._ffi-concurrency%29).) In `racket/draw`, on the other hand, some uses of OOP are because the library was [formerly implemented in C++](https://web.archive.org/web/20101211053922/http://blog.racket-lang.org/2010/12/racket-version-5.html): the [`dc<%>`](https://docs.racket-lang.org/draw/dc___.html) interface makes sense, but I don’t think many of us would advocate making [`color%`](https://docs.racket-lang.org/draw/color_.html) a class in a new design.

Another quirk is that `db/private/pre`, with the SQLite backend, is used to implement package management (specifically, [`pkg/db`](https://docs.racket-lang.org/pkg/db.html)) and the local documentation database: that’s why the SQLite backend lives in the main Git repository, instead of in [GitHub - racket/db · GitHub](https://github.com/racket/db/). It thus cannot depend on any non-`base` packages. The constraint for `racket/draw` is less dramatic, but it’s still in a somewhat different position than a package not limited to dependencies in the main distribution.

---

<div class="post-metadata">

**Author:** ![hnmdijkema](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/hnmdijkema/32/2629_2.png) [@hnmdijkema](https://racket.discourse.group/u/hnmdijkema)\
**Post date:** [May 20, 2026, 10:44am UTC](https://racket.discourse.group/t/let-assert-define-return/4208/11 "2026-05-20T10:44:52Z")

</div>

Thank you for your elaborate answer. This is very clear, the way you explained it. I've adjusted my code to use `malloc 'atomic-interior` instead of `'raw`.

Furthermore this makes me think about the construction I've created in `libao-async-ffi-racket.rkt`. I've created an alloc/reuse construct that keeps allocated audio chunks in a list thereby preventing deallocation of these memory chunks. In order to optimize speed and prevent GC as much as possible. However, that didn't resolve audio glitches as much as I hoped in drracket.

Eventually starting the audio playing backend in a 'place' did help the most.

Might it be even contraproductive to do such a thing?
