Authoritative reference for which Nim type patterns are supported across
each foreign-language wrapper (C++ / Python / Rust / Go). Cells are
evidence-backed: ✅ entries are validated by the parity test suite
(runTypeMapTestLibCpp, runTypeMapTestLibPy, runTypeMapTestLibRust,
runTypeMapTestLibGo); ❌ entries are confirmed broken by direct probe;
❓ entries are untested.
Pure C is not in the matrix. The typed-C wrapper is deferred — see
doc/design/CBOR_Refactoring.md §10. Pure-C consumers currently see
only the raw 11-function CBOR ABI and must hand-encode payloads against
it.
CBOR is the only FFI mode. Activate codegen with -d:BrokerFfiApi. The
native per-type C codegen was retired — see doc/design/CBOR_Refactoring.md.
The historical -d:BrokerFfiApiNative and transitional
-d:BrokerFfiApiCBOR flags no longer exist. Restrictions documented in
earlier revisions of this file that referenced api_type.nim /
toCFieldType / the CItem layout no longer apply — every such cell
has been re-probed and migrated to ✅ where the parity tests now lock
the behaviour in.
| Mark | Meaning |
|---|---|
| ✅ | Works end-to-end. Validated by parity test. |
| Compiles but defective; specific defect noted. | |
| ❌ | Rejected at codegen or fails to compile. |
| ❓ | Untested — no library exercises this combination. |
| — | Not applicable. |
The field appears inside the Result<T> payload struct returned by a request method.
| Nim type | C++ | Py | Rust | Go |
|---|---|---|---|---|
bool / intN / uintN / byte / floatN |
✅ | ✅ | ✅ | ✅ |
string |
✅ | ✅ | ✅ | ✅ |
cstring |
✅ | ✅ | ✅ | ✅ |
char |
✅ | ✅ | ✅ | ✅ |
Plain enum |
✅ | ✅ | ✅ | ✅ |
distinct intN (incl. type aliases of primitives) |
✅ | ✅ | ✅ | ✅ |
| Object (all primitive/string fields) — used as the whole result type | ✅ | ✅ | ✅ | ✅ |
| Object (all primitive/string fields) — embedded as an inline field of another object | ✅ ¹ | ✅ ¹ | ✅ ¹ | ✅ ¹ |
seq[byte] |
✅ | ✅ | ✅ | ✅ |
seq[primitive] (e.g. seq[int64]) |
✅ | ✅ | ✅ | ✅ |
seq[string] |
✅ | ✅ | ✅ | ✅ |
seq[Object] (Object has prim/string fields only) |
✅ | ✅ | ✅ | ✅ |
seq[Object<seq>] (the inner object contains its own seq[T]) |
✅ ² | ✅ ² | ✅ ² | ✅ ² |
array[N, primitive] |
✅ | ✅ | ✅ | ✅ |
array[N, string] |
✅ ³ | ✅ ³ | ✅ ³ | ✅ ³ |
array[N, Object] |
✅ | ✅ | ✅ | ✅ |
Option[T] (scalar / string / seq[primitive] / Object) |
✅ | ✅ | ✅ | ✅ |
Opt[T] (results — full parity with Option[T]) |
✅ ⁸ | ✅ ⁸ | ✅ ⁸ | ✅ ⁸ |
tuple[a: T, b: U, ...] (named) |
✅ | ✅ | ✅ | ✅ |
The type appears in the request method signature on the caller side.
| Nim type | C++ | Py | Rust | Go |
|---|---|---|---|---|
bool / intN / uintN / byte / floatN |
✅ | ✅ | ✅ | ✅ |
string / cstring / char |
✅ | ✅ | ✅ | ✅ |
Plain enum |
✅ | ✅ | ✅ | ✅ |
distinct intN |
✅ | ✅ | ✅ | ✅ |
| Object as param (whole-object pass-by-value) | ✅ | ✅ | ✅ | ✅ |
seq[byte] |
✅ | ✅ | ✅ | ✅ |
seq[primitive] |
✅ | ✅ | ✅ | ✅ |
seq[string] |
✅ | ✅ | ✅ | ✅ |
seq[Object] (prim/string fields only) |
✅ | ✅ | ✅ | ✅ |
seq[Object<seq>] |
✅ ² | ✅ ² | ✅ ² | ✅ ² |
array[N, primitive] |
✅ ⁴ | ✅ ⁴ | ✅ ⁴ | ✅ ⁴ |
array[N, string] |
✅ ³ | ✅ ³ | ✅ ³ | ✅ ³ |
array[N, Object] |
✅ ⁵ | ✅ ⁵ | ✅ ⁵ | ✅ ⁵ |
Option[T] (scalar / string / seq[primitive] / Object) |
✅ | ✅ | ✅ | ✅ |
Opt[T] (results — full parity with Option[T]) |
✅ ⁸ | ✅ ⁸ | ✅ ⁸ | ✅ ⁸ |
The field appears in an EventBroker(API) object — fired by Nim, delivered to a closure registered via on_<event>.
| Nim type | C++ | Py | Rust | Go |
|---|---|---|---|---|
bool / intN / uintN / byte / floatN |
✅ | ✅ | ✅ | ✅ |
string / cstring / char |
✅ | ✅ | ✅ | ✅ |
Plain enum |
✅ | ✅ | ✅ | ✅ |
distinct intN |
✅ | ✅ | ✅ | ✅ |
seq[primitive] |
✅ | ✅ | ✅ | ✅ |
seq[string] |
✅ | ✅ | ✅ | ✅ |
seq[Object] (prim/string fields) |
✅ | ✅ | ✅ | ✅ |
seq[Object<seq>] |
✅ ² | ✅ ² | ✅ ² | ✅ ² |
array[N, primitive] |
✅ | ✅ | ✅ | ✅ |
array[N, string] |
✅ ⁶ | ✅ ⁶ | ✅ ⁶ | ✅ ⁶ |
array[N, Object] |
✅ ⁷ | ✅ ⁷ | ✅ ⁷ | ✅ ⁷ |
Option[T] (scalar / string / seq[primitive] / Object) |
✅ ⁹ | ✅ ⁹ | ✅ ⁹ | ✅ ⁹ |
Opt[T] (results — full parity with Option[T]) |
✅ ⁸ | ✅ ⁸ | ✅ ⁸ | ✅ ⁸ |
-
Object as inline field works end-to-end on all wrappers. Validated by
test_nested_obj_inline_fieldagainst theNestedObjRequestbroker, whose result struct carries aTagdirectly as a field (not viaseq/array/Option). The wrappers emit the nested struct + itsJSONCONS_ALL_MEMBER_TRAITS/ dataclass /#[derive(Deserialize)]/ cbor-tag registration inline and round-trip the values byte-identical. -
seq[Object<seq>]works end-to-end on all wrappers. Validated bytest_list_inners_result_*,test_bulk_inners_param_roundtrip, andtest_inners_updated_eventin the parity suite.Innercarriesseq[byte]inside, then sits insideseq[Inner]on the outer broker — the very shape earlier revisions of this file marked ❌. The historical restriction was a side-effect of the retired native ABI'sCItemlayout requiring simple-ident field types; the CBOR codegen has no such restriction. -
array[N, string]works end-to-end on all wrappers. Validated bytest_fixed_str_array_result(§1) andtest_set_tags_array_param(§2). Wrappers translatearray[N, string]to their variable-length list types (std::vector<std::string>,List[str],Vec<String>,[]string); the Nim side range-checks the length on decode. If the wrapper passes a length other thanNthe broker returns a cleanerr(...). -
array[N, primitive]as request param — validated bytest_sum_prim_array_paramforarray[4, int32]. Same length-on-decode semantics as footnote 3 — the wrapper passes a length-N vector / list / slice and the Nim side validates. -
array[N, Object]as request param works end-to-end on all wrappers. Validated bytest_set_slots_obj_array_param: theSetSlotsRequestbroker accepts a fixed-size array ofSlotstructs, joins theirnamefields, and returns the result — exercising both the wrapper-side serialization of an array of nested objects and the Nim-side decode + provider invocation. -
array[N, string]in events works end-to-end on all wrappers. Validated bytest_str_array_eventagainst theStrArrayEventbroker (fired byTriggerStrArrayRequest). Wrappers deliver the four strings as their natural list type (std::span<const std::string>,List[str],Vec<String>,[]string). -
array[N, Object]in events — validated bytest_fixed_obj_array_event. The wrappers deliver the slots as a length-N list/span of the typed element struct. -
Opt[T](results,Opt[T] = Result[T, void]) has full parity withOption[T]— it is a drop-in replacement in any position. At codegen everyOpt[...]head is canonicalized toOption[...](canonOptHeadinapi_schema.nim), so all four wrappers emit the identicalstd::optional<T>/Optional[T]/Option<T>/*Tsurface and CDDL (T / null). At runtimeOptbinds the same four CBOR hooks asOption(shouldWriteObjectField/write/read/isFieldExpected, inapi_cbor_codec.nim) and rides the same MT case-object codec branch (mt_codec.nim), so the wire bytes are byte-identical to the equivalentOption[T], present and absent. Directly validated as a request RESULT field by theOptWrapScalarRequest/OptWrapStringRequest/OptWrapObjRequestbrokers (Opt[int32]/Opt[string]/Opt[Tag]) —opt_wrap_*assertions in the Python parity suite plus the Nim parity suite, with all fourrunTypeMapTestLib{Cpp,Py,Rust,Go}matrices building and running the generated wrappers green. Directly validated as an event payload field byOptWrapByteSeqEvent(Opt[seq[byte]], the parity twin ofOptByteSeqEvent) —test_opt_wrap_byte_seq_event*in all four foreign harnesses plus the Nim parity suite. The parameter position inherits the identical canonicalized codegen + runtime path. Nesting composes:seq[Opt[T]],Opt[seq[byte]],Table[K, Opt[V]], andarray[N, Opt[T]]all resolve through the same recursion. (Opt[T]is the only supported optional besidesOption[T]; both share every green cell above.) -
Option[T]in event payloads works end-to-end on all wrappers. Validated bytest_opt_byte_seq_event*againstOptByteSeqEvent(Option[seq[byte]]), which the wrappers deliver to theon_<event>closure asstd::optional<Bytes>/Optional[bytes]/Option<Vec<u8>>/*[]byte, present and absent. The three event-callback codegen helpers (eventCallbackParamType/eventCallbackArgExpr/eventCallbackInvokeSetup) share the sameoption[branch as the result/param mappers, so the other inner shapes (scalar / string /seq[primitive]/ Object) map by the identical path.
To stay safely inside the green cells:
- Prefer
seq[T]overarray[N, T]when N varies between instances. Both work;seq[T]is naturally variable-length whilearray[N, T]enforces a runtime length check on decode. Use the array form when N is a true protocol-level invariant. - Composite object fields are fine. A registered Object can carry
seq[byte],seq[T],array[N, T],Option[T]/Opt[T], or nested objects in any combination. The earlier "keep object field types flat" guidance was a native-ABI artifact and no longer applies. - Nested types auto-register. Plain Nim
object/enum/distincttypes referenced in a broker signature are discovered and registered automatically — noApiTypeannotation needed.
A common shape worth analysing in detail:
type ContentTopic* = string # or `distinct string`; same answer
type Timestamp* = distinct int64
type WakuMessage* = object
payload*: seq[byte]
contentTopic*: ContentTopic
meta*: seq[byte]
version*: uint32
timestamp*: Timestamp
ephemeral*: bool
proof*: seq[byte]Field-by-field, every type is in a green row of all three sections
(seq[byte], string-aliased, uint32, distinct int64, bool).
| Position | Status | Notes |
|---|---|---|
Request result type — Future[Result[WakuMessage, string]] |
✅ | All fields green; same shape as the validated Tag-style objects. |
Event payload — EventBroker(API): type WakuMessageReceived = object with these fields |
✅ | Same shape as TagSeqEvent / PrimScalarEvent, all green in the parity matrix. |
Request parameter — proc signature(msg: WakuMessage) |
✅ | Object-as-param is supported on all wrappers (Section 2). |
| Field of another registered Object | ✅ | Inline-nested Object validated by test_nested_obj_inline_field (footnote 1). |
seq[WakuMessage] — batched delivery |
✅ | Covered by the seq[Object<seq>] row — WakuMessage carries seq[byte] fields, the exact composite-inside-element shape (footnote 2). |
// Rust — pass the whole object directly
let r = lib.send(msg.clone())?;# Python — identical
r = lib.send(msg)// C++ — identical
auto r = lib.send(msg).value();// Go — identical
r, err := lib.Send(msg)When adding a probe to close one of the ❓ cells:
- Add the type and a request/event using it to
test/typemappingtestlib/typemappingtestlib.nim. - Add a corresponding assertion to the parity tests:
test/typemappingtestlib/test_typemappingtestlib.cpptest/typemappingtestlib/test_typemappingtestlib.pytest/typemappingtestlib/rust_test/src/main.rstest/typemappingtestlib/go_test/main.go
- Run all four
runTypeMapTestLib{Cpp,Py,Rust,Go}tasks. Run them sequentially — they share one Nim cache and output.dylib, so concurrent runs clobber each other's object files and surface as spurious "Undefined symbols" link errors. - Update the relevant cell in this document with the result.
When a defect is found, document it in the footnotes with file/line evidence, leave the failing probe out of the live test code (it would block CI), and keep the matrix entry accurate.