Skip to content

fix(uniffi): register the Bytes custom type once, in livekit-common - #1343

Merged
pblazej merged 6 commits into
mainfrom
blaze/uniffi-bytes-remote-type
Aug 24, 2026
Merged

fix(uniffi): register the Bytes custom type once, in livekit-common#1343
pblazej merged 6 commits into
mainfrom
blaze/uniffi-bytes-remote-type

Conversation

@pblazej

@pblazej pblazej commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

bytes::Bytes was registered with custom_type! in both livekit-uniffi and livekit-datatrack. A remote type can only be registered once per UniFFI component: each emits its own public typealias Bytes and FfiConverterTypeBytes, and cargo-swift compiles every component file into one Swift module — invalid redeclaration of 'Bytes'. UniFFI's own duplicated helpers coexist because they're fileprivate; custom_type! emits public, so they don't. Patched until now by a perl regex in swift-workarounds, which is deleted here.

livekit-common now owns the registration and every component borrows it:

uniffi::use_remote_type!(livekit_common::Bytes);

Owning a registration means being a component — custom_type! resolves crate::UniFfiTag, and bindgen rejects metadata belonging to no namespace (Unknown namespace for CustomType). So livekit-common gains an optional uniffi feature, enabled transitively by livekit-uniffi, and a third generated Swift file. That file had to be added to uniffi-swift.yml — the publish step enumerates sources rather than globbing, so an unlisted component silently ships a package that can't build. Costs 4,160 B on the cdylib (1,107,856 → 1,112,016), leaving 66 KiB under the iOS gate.

Verified: livekit-common and livekit-datatrack still build without the feature; the three-file Swift module compiles; client-sdk-swift main builds clean against the generated package, tests included; the xcframework carries all three headers in one framework modulemap.

Alternatives for the collision

Option Verdict
Keep the perl regex Fragile — matches literal generated formatting, so it silently no-ops when uniffi's Swift templates shift — and Swift-only, so Kotlin and Python kept shipping two declarations.
Let livekit-datatrack own it Free (80 B, no new component), but an unintuitive home for a generic byte buffer, and any future component would have to depend on livekit-datatrack to borrow it. Was the first version of this PR.
Collapse to one component — drop uniffi from livekit-datatrack and mirror its types with #[uniffi::remote] Removes the class outright and returns the duplication below. But ~150 lines of mirrored Rust, hand-written adapters for the two with_foreign traits, and dropping #[non_exhaustive] from two enums. Separate PR.

Upstream: #2933 (open) is this bug, and this PR takes the external-type route it proposes. #2126 (open) — no #[uniffi::remote(Trait)] in 0.31 or 0.32 — is why collapsing needs trait adapters. #2802 (open) is the same conflict across separate UniFFI libraries.

Alternatives for the duplication

Not addressed here. Each component re-emits the whole FFI runtime: ≈19 KB across three of them — 8.3 KB native and ~10.6 KB of Swift code+data, from two measured per-component deltas of ~4.2 KB and 5,287 B.

Option Verdict
Hoist the shared Swift runtime into one internal file Worse — prototyped at +14,592 B on the linked binary, because it destroys the per-file specializations that make the duplicate copies cheap. Buys ~5% compile time.
Wait for upstream to emit the common part once Asked for twice, declined twice. Not in flight.
Collapse to one component The only thing that actually returns it.

Upstream: #408 / #409 (closed) asked to eliminate re-declared Swift helpers and were resolved with the UNIFFI_SHARED_H guard plus fileprivate — duplicates left to coexist rather than removed. #2257 (closed) asked to combine the per-crate outputs; closed without a feature. #2153 (open) is the only live proposal that would share the runtime, and also documents the Python breakage: bindgen writes from . import livekit_datatrack into a flat out-dir with no __init__.py, giving ImportError: attempted relative import with no known parent package. #2930 (open) is the Swift → bindings-pipeline migration such a change would land on.

`bytes::Bytes` was registered with `custom_type!` in both livekit-uniffi and
livekit-datatrack. They are separate UniFFI components, so each emitted its own
`typealias Bytes` and `FfiConverterTypeBytes` — and cargo-swift compiles both
component files into one Swift module, so they collided.

Borrow datatrack's registration with `use_remote_type!` instead of adding a
second one. The type is then emitted once, in the component that owns it, which
makes the `swift-workarounds` perl task unnecessary — and that task has to go in
the same commit, since it would otherwise strip the only remaining definition.

Unlike the regex, this is compile-checked and also fixes Kotlin and Python,
which were still shipping two `Bytes` declarations.

Upstream: mozilla/uniffi-rs#2933

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pblazej
pblazej requested a review from ladvoc as a code owner August 20, 2026 11:00
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Changeset ✓

This PR includes a changeset covering all affected packages:

Package Bump
livekit patch
livekit-api patch
livekit-common patch
livekit-data-stream patch
livekit-datatrack patch
livekit-ffi patch
livekit-uniffi patch

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

@pblazej
pblazej requested a review from 1egoman August 20, 2026 11:13
Comment thread livekit-uniffi/src/common.rs Outdated
@ladvoc

ladvoc commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Wait, what happens if you simply do this:

uniffi::use_remote_type!(bytes::Bytes);

At least the docs for this macro imply this would work.

@pblazej

pblazej commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@ladvoc looks like it won't work:

error[E0425]: cannot find type `UniFfiTag` in crate `bytes`

The first path segment isn't the crate that defines the type — it's the crate whose registration you're borrowing. The macro expands to <Bytes as FfiConverter<bytes::UniFfiTag>>, and bytes isn't a UniFFI crate, so it has no UniFfiTag. The docs are genuinely misleading here: their example borrows from another uniffi crate, so the path reads like a plain type path. You need a crate that called setup_scaffolding!().

A remote type can only be registered once per UniFFI component, so owning the
registration in livekit-datatrack made every other component borrow from a crate
that has nothing to do with byte buffers. Register it in livekit-common instead,
which is where shared foundational types already live, and have each component
borrow it with `use_remote_type!`.

Owning a registration means being a component: `custom_type!` resolves
`crate::UniFfiTag`, and bindgen rejects type metadata belonging to no namespace
("Unknown namespace for CustomType"). So livekit-common gains an optional
`uniffi` feature, enabled transitively by livekit-uniffi, and a third generated
Swift file. That file has to be listed in uniffi-swift.yml — the publish step
enumerates sources rather than globbing, so an unlisted component silently ships
a package that cannot build.

Costs 4,160 bytes on the release cdylib (1,107,856 -> 1,112,016), leaving 66 KiB
under the iOS size gate.

Verified: livekit-common and livekit-datatrack still build without the feature;
the three-file Swift module compiles; client-sdk-swift main builds clean against
the generated package, tests included; the xcframework carries all three headers
in one framework modulemap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pblazej pblazej changed the title fix(uniffi): reuse livekit-datatrack's Bytes converter instead of re-registering it fix(uniffi): register the Bytes custom type once, in livekit-common Aug 21, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

pblazej and others added 3 commits August 21, 2026 08:57
The publish step enumerated each generated source, so a new UniFFI component
would have shipped a package missing its types — and the action only aborts on
listed-but-absent files, never on an unlisted one.

livekit/publish-xcframework-action#5 makes `files` sources path patterns, the
same convention as actions/upload-artifact and softprops/action-gh-release, with
a trailing-slash destination meaning "directory, keep basenames". Match
`Sources/<name>/*.swift` so adding a component needs no workflow change; a
pattern matching nothing still fails the job.

Pinned to that PR's head commit; repin to the merge commit once it lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It is only used by `ffi_types`, which is gated on the `uniffi` feature, so a
consumer that doesn't want the FFI registrations shouldn't take the dependency
edge. Without the feature the crate's only direct dependency is livekit-protocol
again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
livekit/publish-xcframework-action#5 is merged; pin the merge commit rather than
the PR branch head.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@1egoman 1egoman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice, I'm a fan of the livekit-common approach!

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 3 additional findings in Devin Review. (Configure)

Open in Devin Review

@pblazej
pblazej merged commit 2b254b6 into main Aug 24, 2026
26 checks passed
@pblazej
pblazej deleted the blaze/uniffi-bytes-remote-type branch August 24, 2026 08:08
@knope-bot knope-bot Bot mentioned this pull request Aug 24, 2026
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.

3 participants