Skip to content

quest(m1): binding namespace spike is a no-go - #5460

Merged
kixelated merged 5 commits into
mainfrom
quest/m1/ffi-crate-spike
Oct 13, 2026
Merged

kixelated merged 5 commits into
mainfrom
quest/m1/ffi-crate-spike

Conversation

@kixelated

@kixelated kixelated commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Spike for quest/m1/ffi-crate-spike.md. Verdict: no-go. The wrappers keep supplying the binding namespaces, and the migration quest (quest/m1/ffi-crate-split.md) is deleted, as the spike's plan specified for a measured no-go.

Why

The split exists so a layer can reuse the net noun it wraps: media.GroupConsumer(group: GroupConsumer). UniFFI 0.32.2 cannot generate that. uniffi_bindgen's TypeUniverse keys every type a crate references by bare name, so a media crate that defines GroupConsumer and also takes the net GroupConsumer fails with conflicting types in every generator built on ComponentInterface. Python, which uses the newer pipeline, generates without an error but silently drops media.GroupConsumer and media.Frame. The check is unchanged on uniffi main, and the Go, Dart, and C++ forks inherit it from upstream uniffi_bindgen, so a fork patch cannot fix it.

Distinct names do work across crates, but that only turns MoqMediaGroupConsumer into media.MediaGroupConsumer. The wrappers already ship media.GroupConsumer.

Evidence

The spike was a standalone two-crate workspace: net (namespace moq) and media, linked into one libmoq_ffi cdylib/staticlib. net exports an object (GroupConsumer, sync and async methods), an enum error with fields and an exported Display (MoqError), a record (Frame), and an enum (ErrorScope). media wraps the object, returns a media record that holds the net record and enum, throws the net error from sync and async calls, and nests the net object in a media enum variant. Each generator ran against the built library, and each consumer was compiled and run against it.

Generator One module per crate Same noun in both layers Net object / error / record / enum from media
Go (uniffi-bindgen-go fork) yes, moq/ and media/ packages refused: conflicting types works (needs go_mod config); go run passes
Dart (uniffi-dart fork) yes, moq.dart and media.dart, one native asset id refused works; dart run passes
C++ (uniffi-bindgen-cpp fork) yes, moq:: and media::, shared guarded runtime refused works; g++ link and run pass
Generated C (same fork, --lang c) yes refused refused: the C backend rejects external types (a fork patch, not attempted given the above)
Kotlin (uniffi 0.32.2) yes, uniffi.moq and uniffi.media refused works; kotlinc + JNA run passes
Swift (uniffi 0.32.2) yes, but UniFFI documents that all files must compile into one Swift module (flat) refused works in one module; as separate modules it needs import moq added to media.swift, which the generator does not emit
Python (uniffi 0.32.2) yes, moq.py and media.py; maturin builds one wheel with one .so silently drops the media type works

Packaging stays one native library on every platform: Go links one staticlib from both cgo packages, Dart shares one asset id, Kotlin and Python load one .so, and C++ compiles both namespaces against one library.

Size

Each extra UniFFI crate adds its own 53 ffi_<crate>_* scaffolding exports (RustBuffer and rust_future entry points), plus the code behind them. The same items as one crate vs. two crates, release profile (fat LTO, strip = "debuginfo"): 460,088 B vs. 507,800 B for the cdylib, so about +47.7 KB per crate. Six layer crates would add about 240 KB to today's moq-ffi release cdylib (29,177,272 B on x86_64 Linux with default features, so about +0.8%). The real moq-ffi was not split, because the generator verdict already decided the question.

Quest changes

  • Delete quest/m1/ffi-crate-spike.md (done) and quest/m1/ffi-crate-split.md (abandoned on this no-go).
  • quest/m1/ffi-shape/README.md: records the rejection and when to revisit.
  • New quest/m1/ffi-shape/stream-producer-name.md, required by the FFI shape epic: the open media.ContainerStreamProducer naming question from feat(ffi): decode a raw group as media, for arrival-order reads #5356, which the deleted migration had absorbed.
  • quest/m1/cpp-generated-shape.md and quest/m1/c/README.md no longer wait on the split.
  • Codecs proceeds on the wrapper-namespace shape it already describes.

Decisions

Does the no-go stand as final?

  • ✅ Accept the no-go (maintainer, 2026-10-13). Keep the wrapper namespaces and drop the crate split, so Codecs starts on the wrapper-namespace shape.
  • Pursue upstream in uniffi-rs: teach TypeUniverse to namespace external types, then revisit the split.
  • Fork uniffi_bindgen: patch the bare-name check ourselves and carry it in every generator fork.

Public API and wire impact

None. Quest files only.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 3 commits October 13, 2026 13:54
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UniFFI 0.32 keys a crate's types by bare name, so a layer crate cannot reuse the net noun it wraps (media.GroupConsumer over GroupConsumer). Every generator refuses it except Python, which drops the type silently. Keep the wrapper namespaces and delete the crate-split migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Spike outcome: no-go. Codecs (quest/m1/ffi-shape/codec.md) can start on the wrapper-namespace shape.

Open decision for the maintainer: do we accept the no-go as final?

  1. Accept the no-go (recommended). Merge this, keep the wrapper namespaces, and drop the split. A split that keeps names distinct only turns MoqMediaGroupConsumer into media.MediaGroupConsumer, which costs about 240 KB and a C backend patch, and the wrappers already ship media.GroupConsumer.
  2. Pursue it upstream. File a mozilla/uniffi-rs issue (and maybe a PR) to key TypeUniverse by module path and qualify imports in Kotlin, Dart, Python, and Swift, and track that as a waiting-on-upstream quest. This is a large upstream change touching every backend, and the Go, Dart, and C++ forks would then need rebasing.
  3. Fork uniffi_bindgen. We would maintain a fork of the core generator as well as the three backend forks. Not recommended.

Spike harness (local only, not committed): a two-crate net/media workspace linked into one libmoq_ffi. Go, Dart, C++, Kotlin, Swift, and Python consumers were all compiled and run against it. Same-noun generation was attempted in all seven backends (counting generated C).

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review October 13, 2026 22:49
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 26 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d0237ac3-bbd7-4545-b621-546550cee54f
📥 Commits

Reviewing files that changed from the base of the PR and between fb7310b and d32c9c6.

📒 Files selected for processing: 7
  • quest/m1/README.md
  • quest/m1/c/README.md
  • quest/m1/cpp-generated-shape.md
  • quest/m1/ffi-crate-spike.md
  • quest/m1/ffi-crate-split.md
  • quest/m1/ffi-shape/README.md
  • quest/m1/ffi-shape/stream-producer-name.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T22:57:25.772008Z d32c9c6 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef103c60c2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread quest/m1/ffi-shape/README.md Outdated
Comment on lines +65 to +69
- Still open from #5356: whether the import-side
`MoqMediaContainerStreamProducer` (`media.ContainerStreamProducer`) is
named by role. "Container" there means an importer that recovers framing
from a raw byte stream and may publish several tracks, unlike the
per-track `media.TrackConsumer` and `media.GroupConsumer`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the unresolved API name as a tracked prerequisite

Track this decision in its own quest or assign it to one of this quest's required children. This commit deletes ffi-crate-split.md, the only task that owned reviewing this import-side name, but replaces it with an unlinked “Still open” note outside the Required list. When Codecs and Layers guide finish, FFI shape can therefore be retired and the binding release can proceed without settling the public name, even though naming decisions must be put to the maintainer.

AGENTS.md reference: AGENTS.md:L24-L24

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. d32c9c6 moves it into quest/m1/ffi-shape/stream-producer-name.md, listed under the FFI shape epic's Required, so the epic cannot retire and the binding release cannot ship without the maintainer settling the name.

(Written by Claude Opus 5.5)

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed commit: ef103c6.

No actionable bugs found in these quest-only changes. Keeping wrapper namespaces follows the accepted no-go, removes the rejected split prerequisites, and preserves the unresolved import-side naming question. The separate task-ownership concern is already recorded at #5460 (comment); I found no runtime or packaging defect to add.

Verification: GitHub-only inspection of the changed plans, remaining references and current FFI configuration. The spike harness and generator logs are local-only, so I could not independently reproduce the reported experiments or size measurements. No tests or builds run.

The deleted crate split owned this naming review; an unlinked note in
the epic's settled shape could be skipped when the epic retires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merging on the maintainer's decision: ✅ accept the no-go as final. The wrapper namespaces stay, the crate split is dropped, and Codecs (quest/m1/ffi-shape/codec.md) starts on the wrapper-namespace shape. Not chosen: pursuing namespaced external types upstream in uniffi-rs, or forking uniffi_bindgen.

Since the last review: merged main (no conflicts, quest check passes) and addressed Codex's P1 by moving the open media.ContainerStreamProducer naming question into its own quest, quest/m1/ffi-shape/stream-producer-name.md, which the FFI shape epic now requires.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit 90776f9 Oct 13, 2026
4 checks passed
@kixelated
kixelated deleted the quest/m1/ffi-crate-spike branch October 13, 2026 23:03
@kixelated

Copy link
Copy Markdown
Collaborator Author

Follow-up after merge: the maintainer asked for two more checks. The verdict is still no-go until mozilla/uniffi-rs#3026 is resolved.

  • Rename trick: give the colliding types distinct Rust names, then rename them back per language. This works only in Python and Go (Go needs a small fork patch). Kotlin and Dart shadow the external type with the local one, Swift reports it as ambiguous or shadowed, C++ misplaces converters or detects a cycle, and generated C still refuses external types.
  • Upstream: filed Library mode: a type cannot reference an external type with the same name mozilla/uniffi-rs#3026 with a two-crate repro and a fix sketch.

The evidence table and the quest update linking the issue are in #5473.

(Written by Claude Opus 5.5)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant