# Out-parameters in Racket

**URL:** <https://racket.discourse.group/t/out-parameters-in-racket/3855>\
**Category:** Show & Tell\
**Created:** [July 16, 2025, 9:52am UTC](https://racket.discourse.group/t/out-parameters-in-racket/3855 "2025-07-16T09:52:38Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![bakgatviooldoos](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/bakgatviooldoos/32/1381_2.png) [@bakgatviooldoos](https://racket.discourse.group/u/bakgatviooldoos)\
**Post date:** [July 16, 2025, 9:52am UTC](https://racket.discourse.group/t/out-parameters-in-racket/3855/1 "2025-07-16T09:52:38Z")

</div>

Hi, Racket Discourse.

Long time no see. I have `C#` coming out my ears, lately, so I thought to make a bit of a racket to clear the air... 🥁.

I really enjoy using out-parameters in `C#`, and I think they make some nice patterns more ergonomic.

For those of you who are unfamiliar with the concept, an out-parameter is a parameter which allows a method (or procedure, really), to return multiple values, by exposing the assignment of reference values among the formal parameters. These out-parameters can then be assigned to in the body of the method-definition, and read from at the call-site, like so:

```csharp
var isPresent = dict.TryGetValue(key, out var value);
    ^^^^^^^^^ ^^^^^^^^^^^^^^
   | the out-parameter, which is either null or the value of the key, if it exists
   |
    a boolean return value, depending on whether the dictionary contains the value

if (isPresent)
{
    doSomething(value);
}

```

In `C#`, for example, these out-parameters carry additional constraints, like the requirement of assigning a value to one if not nullable before a method body exits.

The out-parameters can also be passed down into other out-parameter methods, like so:

```csharp
public bool Strategy<TFind>
(
    PathSearchPoolUpdate<TPath> update,
    [NotNullWhen(true)] [MaybeNullWhen(false)] out TFind target
)
    where TFind : ApiPath<TPath>
{
    IEnumerable<IPathSearch<TPath>> pool = [this];
    while (pool.Any())
    {
        var next = pool.First();
        if (next.On(out target)) return true;
                    ^^^^^^^^^^
                    we pass the target out-parameter down into the `next.On` method
        else pool = update(next, pool.Skip(1));
    }
    target = default; return false;
}

```

My thinking was that this seems very similar to what a "parameter" is in Racket, so why not try to build such a feature with those as the building-blocks, so to speak?

The plan was to use `#%top` and `#%app` (although I am still on the fence about the latter), to produce and consume these values.

Thanks to @Eutro and @samth for their packages, [`cadnr`](https://github.com/eutro/cadnr/tree/main) and [`fancy-app`](https://github.com/samth/fancy-app/tree/master), which have been invaluable in figuring out the story, so far.

The idea is that one declares an `out-parameter` arbitrarily, using a reader-macro called `#out`. This reader-macro takes the next identifier after it and adds to it a syntax-property identifying it as an `'out-parameter-identifier`.

There are basically two distinct "contexts" in which an `out-parameter` may appear:

- a **producer** context, i.e. among the formals of a procedure of one or more `out-parameters`,
- a **consumer** context, i.e. a call to a procedure of one or more `out-parameters`.

In a _consumer_ context, the system either creates a new `out-parameter` on the spot and records it for later use, or uses an existing one, if present.

It knows how to do this, because of `#%top`, which is called upon when an unbound identifier is encountered. That is to say, one knows exactly when an identifier has been recorded as an `out-parameter` or not, by the fact that `%#top` is looking at it and whether it has the required syntax-property.

The _producer_ context I am still ambivalent about, since it requires "shielding" to my mind, although I suspect that there may still be a more elegant solution.

In any case, there exists a pair of:

- a syntax-parameter, called `current-out-identifiers`, which tracks the identifiers of `out-parameters` during expansion, and
- a normal parameter, called `current-out-parameters`, which encapsulates the visible `out-parameters` in a particular context (at phase-0, that is).

These two values are parameterized over the body of a _producer_, to ensure that `out-parameters` do not "leak" beyond the body, although technically they could be omnipresent due to their identity as **parameters**.

Finally, to allow for parameter elision, i.e. declaring something as an `out-parameter` but ignoring its value, we call on `#%app`, haha.

When `#%app` encounters an application containing one or more `out-parameters` which match the `_` syntax, it replaces those with un-recorded normal parameters (among the tracked identifiers), and assigns to them generated symbols, so they exist in the current parameterization, but are not referenceable in the declaring context.

I have declined to restrict the assignment of `out-parameters` as in `C#`--meaning, that they have to be assigned in the body of an `out-parameter` procedure--because it is not obvious to me how one would keep track of _re-assignment_, i.e. what if I call `#out param` something else by redefining it as `(define param′ param)`.

Technically it will still be assigned to if `param′` is used, but I might not be able to detect it. So, for now, no restrictions on that, although some phase-0 checks could be in order.

As an additional note, due to the utility of `#%top`, it is unnecessary to use a `var` disambiguator to distinguish between ~~"producer" and "consumer" contexts~~ fresh and known identifiers, as in `C#`.

* * *

Now for some examples:

```scheme
#lang reader "reader.rkt"

(require
  "syntax.rkt")

;; produce an out-parameter:
(define (sub1? n #out m)
  (and (< 0 n) {m (- n 1)}))

;; consume out-parameters:
(define n 4)
(and
 (sub1? n #out n-1)
 (sub1? {n-1} #out n-2)
 (sub1? {n-2} #out n-3)
 {n-3})
;=> 1

;; produce an out-parameter
(define (hash-ref? hash key #out val)
  (and (hash-has-key? hash key) {val (hash-ref hash key)}))

;; consume out-parameters
(define json
  (hash 'person
        (hash 'details
              (hash 'name 'john
                    'surname 'donn))))
(and
 (hash-ref? json 'person #out person)
 (hash-ref? {person} 'details #out details)
 (hash-ref? {details} 'name #out name)
 {name})
;=> 'john

(define (div/mod x n #out remainder)
  (define-values (quo rem) (quotient/remainder x n))
  (and {remainder rem} quo))

(define quo (div/mod 12 5 #out rem))

(values quo {rem})
;=> 2 2

```

And it works with arrows:  
 ![2025-07-16113811-out-params](https://global.discourse-cdn.com/free1/uploads/racket/original/2X/7/7409d979305041c16b75ca56cfbb3d0e18a144cd.gif)

* * *

P.S. tell me what you think of `#⎋` as opposed to `#out` as the reader-macro symbol. I kind of like it, but it is very faint on some displays.

P.P.S. although just an experiment still, any interested Racketeers are happy to have a look, here:

> <https://gist.github.com/bakgatviooldoos/990d717b74b37fa5bf08bbf6f463ed62>
>
> There are more than three files. show original

_Documentation and more examples/utilities still to come, time allowing._

---

<div class="post-metadata">

**Author:** ![EmEf](https://avatars.discourse-cdn.com/v4/letter/e/53a042/32.png) [@EmEf](https://racket.discourse.group/u/EmEf)\
**Post date:** [July 16, 2025, 12:07pm UTC](https://racket.discourse.group/t/out-parameters-in-racket/3855/2 "2025-07-16T12:07:17Z")

</div>

In the 1990s a now-famous type researcher worked with a colleague. She ported a large C++ program, where a similar pattern can be used. The resulting code contained a large number of procedures that consumed boxes to be filled with return values that indicated whether they were filled (if possible). I spent weeks analyzing this code with a couple of static analysis tools. They all faltered on such “out parameters”. In the end, I had to refactor all these procedures to use multiple values (actually on occasion different number of return values). The analysis tools could then cope. (Yes, our contract and type systems don’t do too well with these patterns either.)
