zerobus-sdk / go
CLAUDE.md94 starsChanged 10 days agoApache-2.0View on GitHub
Go wrapper around the Rust core via cgo and the C FFI library. When writing or reviewing client/example code, follow the idiomatic async flow. IngestRecordOffset() and IngestRecordsOffset() (on ZerobusStream), as well as IngestBatch() (on ZerobusArrowStream), return as soon as the record is queued; the SDK sends it and tracks its acknowledgment in the background. - Ingest in a loop, then call Flush() to confirm durability — once for a bounded batch, or periodically for a long-running stream. - Acks are…
In this repository6 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- cpp/CLAUDE.mdCLAUDE.md
- java/CLAUDE.mdCLAUDE.md
- python/CLAUDE.mdCLAUDE.md
- rust/CLAUDE.mdCLAUDE.md
- typescript/CLAUDE.mdCLAUDE.md
The file
We split this CLAUDE.md into the rules it gives an agent
9 rules. About 0 are common, 2 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 7
When writing or reviewing client/example code, follow the idiomatic async flow.
- Very specific: line 16
Use WaitForOffset() when a specific record must be confirmed before continuing.
- Distinctive: line 72
Every FFI function that passes Go data uses: create pinner → pin → call C → defer unpin.
- Very specific: line 78
When using custom HeadersProvider (instead of default OAuth):
- Very specific: line 117
Every PR must update go/NEXT_CHANGELOG.md under the appropriate section if it changes user-facing behavior.
- Very specific: line 118
Update go/README.md if the change affects usage, setup, or API surface.
- Very specific: line 119
Add or update examples in go/examples/ for new or modified APIs.
- Distinctive: line 120
Add godoc comments for all new exported types, functions, and methods.
- Very specific: line 128
Do not wait on an ffi/v* GitHub Release.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Go SDK
Go wrapper around the Rust core via cgo and the C FFI library.
Client code patterns (performance)
When writing or reviewing client/example code, follow the idiomatic async flow.
IngestRecordOffset() and IngestRecordsOffset() (on ZerobusStream), as well as
IngestBatch() (on ZerobusArrowStream), return as soon as the record is queued;
the SDK sends it and tracks its acknowledgment in the background.
- Ingest in a loop, then call
Flush()to confirm durability — once for a bounded batch, or periodically for a long-running stream. - Acks are ordered, so if you only need to confirm a group of records, call
WaitForOffset()on the LAST offset — it confirms all prior offsets too. - Use
WaitForOffset()when a specific record must be confirmed before continuing; preferFlush()for bulk durability. Avoid callingWaitForOffset()after every record in a tight loop, since that limits throughput to one record per round-trip. - There is no ack-callback API in the Go SDK; use
Flush()/WaitForOffset()(or the fire-and-forgetIngestRecordNowait/IngestRecordsNowait).
Structure
go/
├── zerobus.go # Public API (ZerobusSdk, ZerobusStream)
├── ffi.go # cgo bindings — calls C functions from zerobus.h
├── arrow_ffi.go # Arrow Flight cgo bindings
├── arrow_stream.go # Arrow stream public API
├── ack.go # Acknowledgment types
├── errors.go # ZerobusError
├── types.go # Configuration types
├── build.go # Build tags and cgo link directives
├── build_rust.sh # Script to build the Rust FFI library
├── lib/ # Pre-built static libraries per platform
│ ├── linux_amd64/libzerobus_ffi.a
│ ├── linux_arm64/libzerobus_ffi.a
│ ├── darwin_amd64/libzerobus_ffi.a
│ ├── darwin_arm64/libzerobus_ffi.a
│ └── windows_amd64/libzerobus_ffi.a
├── tests/ # Integration tests
└── examples/ # Usage examplesBuild commands
Run from go/:
make build— Build Rust FFI lib + Go SDKmake build-rust— Build only Rust FFI layermake build-go— Build Rust FFI (viabuild-rust) then the Go SDKmake test— Run testsmake lint— go vet + cargo clippymake fmt— gofmt + cargo fmt
FFI boundary: cgo + static linking
This is the most memory-management-sensitive wrapper. Key considerations:
Memory ownership
- Opaque pointers: Go holds
unsafe.Pointerto Rust-allocatedCZerobusSdk/CZerobusStream. Rust owns the memory. - Explicit free required:
zerobus_sdk_free(),zerobus_stream_free(),zerobus_arrow_stream_free(). - Finalizers: Both
ZerobusSdkandZerobusStreamregisterruntime.SetFinalizerfor GC-triggered cleanup, but explicitFree()/Close()is preferred for deterministic resource release. - Error strings: C-allocated error messages (
CResult.error_message) must be freed viazerobus_free_error_message()after converting to Go string withC.GoString().
Memory pinning (critical)
Go's GC can relocate heap objects. When passing Go memory to C:
runtime.Pinneris used to pin Go slices/pointers before passing to Rust.- Every FFI function that passes Go data uses: create pinner → pin → call C → defer unpin.
- Never remove
runtime.Pinnercalls — doing so causes "cgo argument has Go pointer to unpinned Go pointer" panics. - Requires Go 1.21+ (the
runtime.PinnerAPI).
Handle ownership for callbacks
When using custom HeadersProvider (instead of default OAuth):
- A
cgo.Handlewraps the Go interface value and prevents GC collection. - The handle is passed to the FFI as
user_data, and its ownership is handed to the FFI along with afree_user_datadestroy callback (goFreeHeadersProvider). The FFI invokes it exactly once on every path — on success after any in-flightget_headersreturns, and on a failed create before returning — and that is wherehandle.Delete()runs. The Go side must therefore never callhandle.Delete()itself, not even when create fails; doing so would double-delete thecgo.Handle(panic). - This replaces the older per-stream handle registry: freeing the handle on
close()could race a recoveryget_headersstill running on a worker thread (use-after-free). - Leaking a handle leaks the Go
HeadersProviderobject and any resources it holds.
Arrow batch cleanup
zerobus_arrow_free_batch_array()must be called after reading unacknowledged batches.- Arrow streams with custom headers use the same FFI-owned handle destroy path.
Breaking change rules
Public API is everything exported (capitalized) in the go/ package:
- Removing or renaming exported types, functions, methods, or struct fields is breaking.
- Changing function signatures is breaking.
- Deprecate with
// Deprecated: Use X instead.godoc comment. - Go module versioning follows git tags (
go/v*). Major version changes require a/v2module path suffix.
Performance notes
- cgo calls have ~100-200ns overhead per invocation. Batch APIs (
IngestProtoRecords,IngestJsonRecords) amortize this. - Record data (
[]bytefor proto,stringfor JSON) is pinned and passed by pointer — no extra copy beyond what cgo requires. - The static FFI library is linked at build time. No runtime loading overhead.
Thread safety
Go SDK is safe for concurrent use from multiple goroutines. Internal synchronization handles concurrent Ingest calls.
Changelog and documentation
- Every PR must update
go/NEXT_CHANGELOG.mdunder the appropriate section if it changes user-facing behavior. - Update
go/README.mdif the change affects usage, setup, or API surface. - Add or update examples in
go/examples/for new or modified APIs. - Add godoc comments for all new exported types, functions, and methods.
Release
- Version source: git tag only (no version file in Go).
- Dispatch
release-go.yml(workflow_dispatch) on the version-bump branch. It builds FFI archives fromrust/and uploadsgo-lib-*artifacts; it does not create a tag, and pushinggo/v*does not start it. Commit the.afiles intogo/lib/on that branch, merge, then create the signedgo/v<semver>tag once on the merge commit. - Go modules resolve via git (pkg.go.dev needs no registry upload). The tagged commit is the module, so
go/lib/must already be in git before the tag. Never move ago/v*tag (Go checksum database). - The publisher Go job scans and uploads
go-libs-scanned. It is not the source of the tagged libs and does not cut a GitHub Release. - Do not wait on an
ffi/v*GitHub Release. That release is for C++/FFI consumers; Go rebuilds FFI fromrust/on the version-bump branch. - On version bump PR: move
NEXT_CHANGELOG.mdcontents toCHANGELOG.md, resetNEXT_CHANGELOG.md.
Config
- Go >= 1.21 required
- CGO_ENABLED=1 required
- Rust toolchain required for building FFI from source
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

