Skip to content

typestates/with_bound

The with_bound macro provides a scoped binding for an endpoint view: within the macro body, the endpoint is in the Bound state and the user code may push / pop through it; on scope exit, the endpoint transitions back to Unbound (or Closed, depending on the shape's destructor contract).

This is the recommended form for the common case of a worker thread that acquires an endpoint, drives it for a bounded period of work, and releases it on exit. The explicit getProducer() / bindToThread() / detach() triple is retained for cases where the endpoint must outlive a single scope (for example, when stored on a long-lived worker object).

See also

with_bound

RAII wrapper template (withBoundEndpoint / withBoundProducer / withBoundConsumer) + Queueable[T] concept per design §5.5.

Purpose

v0.1.0 surfaces two API styles for endpoint binding:

  1. Typestate-guarded surface (existing) — users call q.getProducer(), then bindToThread(), then push(item), then close(). The Unbound → Bound → Closed typestate FSM is visible and provides compile-time guards (no push on Unbound, no double bind, no use-after-close).

  2. RAII surface (this module) — withBoundProducer(q, p): body expands to a block that runs getProducer + bindToThread, executes body with p bound to the Bound[...] endpoint, and runs close on scope exit via defer. The typestate FSM still drives the compile-time guards inside body; the user just doesn't type the transitions.

Both surfaces dispatch to the same per-cardinality push/pop bodies — there is no duplicate implementation. Bug fixes apply uniformly.

The Queueable[T] concept (§5.5.5) is a non-typestate-aware ergonomic surface for library code that wants a single generic Queueable[T]-shaped parameter rather than Queue[T, ccProd, ...] | BQueue[T, ccProd, ...] | Bound[T, Tag, Queue[T, ...]] enumerated arms. Concept resolution is verified across the full Path-C-encoded payload set (ref X / string / seq[U] / POD T).

Path-C transparency (operator directive 2026-06-06)

Both surfaces work uniformly across ref X (via ManagedRef), string / seq[U] (via ManagedSlice), and POD T. The Path-C encoding routing happens INSIDE the existing per-cardinality push/pop bodies; this module never touches encoded slots. Users see Queue[ref Foo] / Queue[string] / Queue[seq[int]] / Queue[int] with the same surface regardless of which API style they choose.

withBoundProducer

template withBoundProducer(queue: var BQueue[T, ccMulti, ccCons, N, P, C]; endpoint: untyped; body: untyped): untyped

BQueue multi-producer RAII wrapper. Acquires a per-thread producer

slot, transitions to Bound, runs body, then transitions to EndpointClosed on scope exit.

Parameters
  • queue (var BQueue[T, ccMulti, ccCons, N, P, C])
  • endpoint (untyped)
  • body (untyped)
Returns

untyped

withBoundConsumer

template withBoundConsumer(queue: var BQueue[T, ccProd, ccMulti, N, P, C]; endpoint: untyped; body: untyped): untyped

BQueue multi-consumer RAII wrapper. Symmetric to

withBoundProducer.

Parameters
  • queue (var BQueue[T, ccProd, ccMulti, N, P, C])
  • endpoint (untyped)
  • body (untyped)
Returns

untyped

withBoundProducer

template withBoundProducer(queue: var Queue[T, ccProd, ccCons, ST, S, MaxThreads]; endpoint: untyped; body: untyped): untyped

Queue producer RAII wrapper. Covers all four cardinality arms

(SPSC / MPSC / SPMC / MPMC) — getProducer on Queue is defined uniformly in endpoint.nim. SPSC absorbed arm (ccProd == ccSingle and ccCons == ccSingle) is debra-free, so bindToThread is a no-op there; the template's shape is unchanged.

Parameters
  • queue (var Queue[T, ccProd, ccCons, ST, S, MaxThreads])
  • endpoint (untyped)
  • body (untyped)
Returns

untyped

withBoundConsumer

template withBoundConsumer(queue: var Queue[T, ccProd, ccCons, ST, S, MaxThreads]; endpoint: untyped; body: untyped): untyped

Queue consumer RAII wrapper. Symmetric to withBoundProducer.

Parameters
  • queue (var Queue[T, ccProd, ccCons, ST, S, MaxThreads])
  • endpoint (untyped)
  • body (untyped)
Returns

untyped

withBoundEndpoint

template withBoundEndpoint(queue, endpoint, body: untyped): untyped

WARNING: this umbrella alias ALWAYS binds a PRODUCER endpoint (it

forwards to withBoundProducer). A consumer-intent call here will SILENTLY acquire a producer slot — there is no role auto-detection. For a consumer endpoint you MUST call withBoundConsumer explicitly. The producer/consumer dispatch (getProducer vs getConsumer) cannot be inferred from the queue type alone, so this alias hard-codes the producer role.

Parameters
  • queue (untyped)
  • endpoint (untyped)
  • body (untyped)
Returns

untyped

Queueable

type Queueable[T] = concept x
    ## Type-class for "anything queue-like accepting / yielding `T`".
    ## 
    ## Resolution: Nim's concept machinery instantiates the body with
    ## `x` bound to a candidate type and checks each expression compiles
    ## and has the stated type. The `var x` form admits both `var Queue`
    ## and `var BQueue` direct-push variants and `Bound` endpoint
    ## variants alike — push/pop on `Bound` take `self: Bound[...]`
    ## (not var), but Nim's overload resolution falls through to those
    ## from the `var x` candidate.
    var qref: typeof(x)
    push(qref, default(T)) is bool
    pop(qref) is Option[T]