Contributor details
| Contributor | Yash Agarwal |
|---|---|
| Organisation | The Apache Software Foundation, Apache Fory |
| Mentor | Chaokun Yang |
| Project | gRPC service code generation for Fory's IDL compiler |
About the project
Apache Fory is a blazingly-fast multi-language serialization framework. Its IDL compiler (foryc) generates model types from Fory IDL, Protocol Buffers, and FlatBuffers schemas across eleven languages.
Fory's IDL compiler could already generate model types for every language it targets, but gRPC service generation was still being added language by language across the project. This project owned the Dart and Swift generators: without them, a Fory IDL service definition produced no client and no server.
Goals
Close that gap for both languages:
- Generate idiomatic gRPC clients and servers whose request and response bodies are serialized with Fory rather than protobuf.
- Support all four gRPC shapes: unary, server-streaming, client-streaming, and bidirectional.
- Support all three schema frontends: Fory IDL, protobuf IDL, and FlatBuffers IDL.
- Prove the generated code interoperates with the other Fory runtimes, not just with itself.
What got merged
Both pull requests are merged into apache/main.
| PR | Title | Merged | Scope |
|---|---|---|---|
| #3723 | feat(compiler): add dart gRPC codegen | 21 Jun 2026 (0ebf1bab4) | 26 files, +2,287 / −244 |
| #3776 | feat(compiler): Add grpc support for Swift | 24 Aug 2026 (676253b6b) | 32 files, +2,543 / −58 |
Each PR went through roughly a dozen maintainer review comments, and the designs below are the post-review shape rather than what I first proposed.
Dart: PR #3723
- Generated surface. A gRPC companion per schema exposing a client and a service base, covering all four gRPC shapes.
- Self-sufficient runtime. My first design required users to install a Fory instance at startup, so a forgotten call meant the first RPC failed. Review established that generated gRPC code must never require manual registration.
- Correct type resolution. Payload types initially used raw IDL names, which broke for imported and nested types; they now resolve through the generator's own emitted type paths.
- Reserved-name guarding. Generated classes inherit from gRPC base types and from
Object, so an RPC namedToStringproduced an invalid override. Codegen now detects those cases and fails with a clear diagnostic. - Interop and CI. A Dart peer is driven in both directions across all three schema frontends, with analyzer and formatter checks moved to where the Dart toolchain actually runs, and the CI action pinned per ASF policy.
Swift: PR #3776
- Generated surface. A gRPC companion per service targeting grpc-swift 1.x, emitting both providers, an async client, service metadata, and the stream adapters a service actually needs.
- Per-thread Fory runtime. The Swift runtime is single-threaded, so each thread builds its own instance from the schema module's configuration. A shared instance had a real race, caught under ThreadSanitizer, and an early key design let two generated modules silently share one resolver.
- A Swift runtime fix. Under reference tracking, Swift wrote a different reference flag for value-type structs than Java, Rust, and C++ do, so Java's reader failed on Swift-produced bytes. Swift now follows the same convention as the other runtimes.
- Symbol-collision preflight. Generation fails before writing files when two declarations would claim one Swift identifier, covering namespace enums, duplicates within a schema, flattened helpers, names that normalize together, and declarations nested inside messages.
- Interop and CI. A Swift peer is driven in both directions across all three schema frontends, alongside an in-process suite covering every streaming shape and a concurrency test, wired into a dedicated CI job. The concurrency suite is written with swift-testing so it can run under ThreadSanitizer, which macOS cannot do for XCTest bundles.
Current state
Both features are complete and shipping in apache/main:
foryc service.fdl --dart_out=./lib --grpc
foryc service.fdl --swift_out=./Sources --grpcDocumentation is published for both languages (Dart, Swift), and cross-language interop with Java runs in CI on every relevant change.
Future work
I intend to keep contributing to Apache Fory beyond the program, picking up open issues across the project rather than limiting myself to the areas I built.
The one known follow-up from this work is Swift 6 strict concurrency: generated companions currently require Swift 5 language mode, because the generated Fory Swift models do not yet declare Sendable conformance. Emitting that from the model generator would unblock it, and it is documented for users in the meantime.
Challenges and lessons
Reference implementations over intuition. Swift's type system and macro model were the areas I had least depth in going into this project, and the analogues from the other runtimes do not transfer cleanly. I worked from the mature Java, Rust, and C++ implementations as the reference specification and verified each assumption empirically rather than inferring it, which is what surfaced the reference-tracking divergence.
Finding the real bug. The Java↔Swift interop failure surfaced as an out-of-bounds error deep inside Java's reference reader. My first fix was on the Java side, and it worked. My mentor pointed out that Rust and C++ interoperate without any Java change, so the divergence had to be in Swift. He was right. Reverting a working fix to find the correct one was the most valuable lesson of the project: a passing test is not evidence that the fix belongs where you put it.
Proving a fix by breaking it. I now revert every fix and confirm the new test fails before trusting it. That caught a regression test of my own that did not actually reproduce the bug it was meant to guard.
Reviewing for degenerate input. Comparing my generator's shape against the already-merged languages found nothing. Almost every review finding was about degenerate input, an empty service, an unusable method name, two type names colliding after normalization, two generated modules sharing a thread and both PRs converged on the same lesson: structural parity is not behavioural parity.
Acknowledgements
The past few months have been the most rewarding engineering I've done. My deepest thanks to my mentor, Chaokun Yang, whose reviews repeatedly found what I had convinced myself was fine. Both PRs are materially better for his patience and rigour. Thank you to the Apache Fory community for welcoming a newcomer working across unfamiliar runtimes, and to Google for organising a program that makes mentored open-source work like this possible.