Returning Result / Option from Inline-C

A fallible C constructor -- one that allocates or acquires a handle in C and can fail (open a MIDI port, connect a socket, parse a file) -- should return a real (Result Handle E) or (Option Handle), not a :ptr<void> and not a magic-sentinel :int (-1, 0-as-absent, INT64_MIN). A sentinel throws away the type the checker could otherwise enforce, and forces every caller to remember the convention.

You do not have to hand-roll the result struct. Every emitted translation unit carries a small set of preamble helpers that build and inspect Option/Result values through the canonical heap layout -- the same one stdlib/option.tur and stdlib/result.tur use -- so a value built in C flows straight into the stdlib accessors (ok?, err?, ok-val, err-val, some?, unwrap) and vice versa.

The helpers

The builders come in three flavours. Prefer the typed _int / _ptr builders: they spell out the payload's cast direction at the call site, so an inline-C author never has to remember whether a pointer payload needs an (int64_t)(intptr_t) widening.

Helper Builds Payload
tur_ok_ptr(void *p) (Result A B), ok pointer handle, widened for you
tur_ok_int(int64_t v) (Result A B), ok integer code, passed as-is
tur_err_ptr(void *p) (Result A B), err pointer handle, widened for you
tur_err_int(int64_t e) (Result A B), err integer code, passed as-is
tur_some_ptr(void *p) (Option A), some pointer handle, widened for you
tur_some_int(int64_t x) (Option A), some integer payload, passed as-is
tur_none() (Option A), none -- (NULL)

tur_none() is the function-call companion to the TUR_NONE macro; use whichever reads better next to the typed builders around it.

Inspectors, for an inline-C body that consumes a Result/Option built elsewhere (the carrier is the int64_t the opaque/Result lowered to):

Helper Reads
tur_is_ok(int64_t r) bool -- is the result ok?
tur_ok_value(int64_t r) the ok payload (as int64_t)
tur_err_value(int64_t r) the err payload
tur_is_some(int64_t o) bool -- is the option some?
tur_opt_value(int64_t o) the some payload

The untyped tur_box_* builders

The typed builders are thin wrappers over the original carrier-level builders, which take the raw int64_t carrier directly:

Helper Builds
tur_box_ok(int64_t v) (Result A B), ok, payload v
tur_box_err(int64_t e) (Result A B), err, payload e
tur_box_some(int64_t x) (Option A), some, payload x
TUR_NONE the none (Option A) (NULL)

These remain valid and are how older inline-C blocks in the tree call into the layout. For a pointer payload they require the explicit tur_box_ok((int64_t)(intptr_t)h) cast -- which is exactly the friction tur_ok_ptr(h) removes. Reach for tur_box_* only when the payload is already an int64_t carrier you are forwarding unchanged; otherwise prefer the _int / _ptr builder that names your intent.

Worked example: an rtmidi-shaped port constructor

A C MIDI binding wraps RtMidiInPtr -- an opaque library handle that rtmidi_in_create_default() returns and that the close call consumes. The constructor can fail (the backend is unavailable, the requested port index is out of range), so it returns a typed (Result MidiIn int): ok carries the handle, err carries an integer status code.

;; A linear opaque over the C library handle -- consumed exactly once by
;; midi-in-close. See opaques-guide.md for the :linear discipline.
(defopaque MidiIn :ptr<void> :linear)

;; midi-in-open : port -> (Result MidiIn int)
;;   ok  = the live RtMidiInPtr, built with tur_ok_ptr (no hand cast)
;;   err = an integer status code, built with tur_err_int
(defn midi-in-open [port : int] : (Result MidiIn int)
  ```c
  RtMidiInPtr h = rtmidi_in_create_default();
  if (h == NULL)        return tur_err_int(1);   /* backend unavailable */
  if (!h->ok)           { rtmidi_in_free(h); return tur_err_int(2); }
  unsigned n = rtmidi_get_port_count(h);
  if ((unsigned)port >= n) { rtmidi_in_free(h); return tur_err_int(3); }
  rtmidi_open_port(h, (unsigned)port, "turmeric-in");
  return tur_ok_ptr(h);
  ```)

;; midi-in-close : consume the handle (linear: exactly one call).
(defn midi-in-close [m : MidiIn] : void
  ```c
  RtMidiInPtr h = (RtMidiInPtr)(intptr_t)m;
  rtmidi_close_port(h);
  rtmidi_in_free(h);
  ```)

The consumer is plain Turmeric -- the stdlib accessors read the C-built value with no special handling:

(defn with-first-port [] : int
  (let [r (midi-in-open 0)]
    (if (ok? r)
      (let [m (ok-val r)]
        (do
          (poll-loop m)
          (midi-in-close m)
          0))
      (err-val r))))          ;; surface the status code on failure

An (Option MidiIn) "open the default port if there is one" variant uses tur_some_ptr / tur_none the same way:

(defn midi-in-default [] : (Option MidiIn)
  ```c
  if (rtmidi_get_port_count_default() == 0) return tur_none();
  RtMidiInPtr h = rtmidi_in_create_default();
  rtmidi_open_port(h, 0, "turmeric-in");
  return tur_some_ptr(h);
  ```)

Why this beats the alternatives

This is the blessed replacement for two anti-patterns that used to spread through spices (see docs/archive/history/no-stdlib-result-builder-for-inline-c.md):

Limitation: only _int and _ptr payloads

v1 ships the monomorphised _int / _ptr builders, which cover an integer error code or an opaque pointer handle -- the shape every audited spice site needs. A (Result MyStruct MyErr) where MyStruct / MyErr are user-defined by-value types is not constructible from inline-C with these helpers: the payload has to fit the single int64_t carrier slot. Wrap the value behind an opaque pointer handle (the rtmidi pattern above) or construct the Result in Turmeric instead.

See also