Skip to content

Make std.Io an explicit, caller-provided dependency (remove hidden global)#95

Merged
koko1123 merged 1 commit into
mainfrom
feat/explicit-io
Jun 11, 2026
Merged

Make std.Io an explicit, caller-provided dependency (remove hidden global)#95
koko1123 merged 1 commit into
mainfrom
feat/explicit-io

Conversation

@koko1123

@koko1123 koko1123 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Why

Zig 0.16's whole point is making the execution model pluggable: networking, HTTP, sleeping, clocks, and randomness live behind std.Io, and the idiom (per the 0.16 language reference) is "explicit passing of context (allocators, I/O handles) over hidden global dependencies."

eth.zig previously manufactured a global blocking Io internally (runtime.defaultIo()), which hid that dependency and prevented callers from running the library on their own event loop or honoring its cancellation/timeouts. This was raised by the community, and it's right.

What

Io is now an explicit parameter everywhere it's needed -- no hidden global remains (rg defaultIo returns nothing):

  • runtime.defaultIo()runtime.blockingIo(): a convenience blocking Io value the caller passes; the library never reaches for it implicitly. milliTimestamp(io) / sleepMs(io, ms) take io.
  • Network constructors take io: HttpTransport.init, WsTransport.connect, WsClient.connect, ws_transport.connectWithReconnect, flashbots.Relay.init, FallbackProvider.init, MevShareClient.init/initMainnet, sse_transport.subscribe/subscribeWithReconnect.
  • Randomness is an Io capability in 0.16: mnemonic.generate(io, ...), keystore.encrypt(..., io, ...).
  • Wrappers inherit it from the transport they wrap (single source of truth): Provider.io() returns transport.io; Wallet, RetryingProvider, NonceManager read provider.io() -- no new params.
// run on your own event loop:
var http = eth.http_transport.HttpTransport.init(alloc, url, my_io);
var provider = eth.provider.Provider.init(alloc, &http); // inherits my_io

// simple blocking case:
var http = eth.http_transport.HttpTransport.init(alloc, url, eth.runtime.blockingIo());

Example 08 threads the real init.io from std.process.Init -- the canonical 0.16 entry-point pattern.

Breaking

Public-API breaking: every transport/client construction now takes an io. Pass eth.runtime.blockingIo() for the previous behavior. Lands in v0.7.0 (not yet released), so the library ships idiomatic from the start.

Verification

812/812 unit tests on 0.16.0 and 0.17-dev, 23/23 Anvil integration tests, all examples build, docs build, zig fmt clean. Includes a test asserting the caller's Io is threaded through to the transport's http.Client.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Breaking Changes
    • Multiple public API function signatures now require an explicit std.Io parameter: HttpTransport.init(), WsTransport.connect(), FallbackProvider.init(), mnemonic.generate(), keystore.encrypt(), and runtime timing/sleep functions.
    • runtime.defaultIo() renamed to runtime.blockingIo().
    • All network and randomness-dependent APIs must now supply an explicit I/O instance during initialization and operation.

…obal)

Zig 0.16 moved networking, HTTP, sleeping, clocks, and randomness behind
the std.Io interface so the execution model is the caller's choice. The
library previously manufactured a global blocking Io internally, which
hid that dependency and prevented callers from running eth.zig on their
own event loop or honoring its cancellation -- the exact thing 0.16 is
designed to make pluggable.

Io is now an explicit parameter everywhere it is needed:
- runtime.defaultIo() -> runtime.blockingIo() (a convenience value the
  caller passes; never used implicitly). milliTimestamp/sleepMs take io.
- Network constructors take io: HttpTransport.init, WsTransport.connect,
  WsClient.connect, ws_transport.connectWithReconnect, flashbots.Relay
  .init, FallbackProvider.init, MevShareClient.init/initMainnet,
  sse_transport.subscribe/subscribeWithReconnect.
- Randomness needs an Io in 0.16: mnemonic.generate(io, ...),
  keystore.encrypt(..., io, ...).
- Wrappers inherit it from the transport they wrap (single source of
  truth): Provider.io() returns transport.io; Wallet, RetryingProvider,
  and NonceManager read provider.io() -- no new params on those.

Pass eth.runtime.blockingIo() for the previous default behavior. Example
08 threads the real init.io from std.process.Init. All examples, tests,
and docs updated.

Breaking change. Verified: 812/812 unit tests on 0.16.0 and 0.17-dev,
23/23 Anvil integration tests, examples build, docs build, fmt clean.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR threads explicit std.Io parameters through the eth.zig library, replacing global default IO with dependency injection. runtime.defaultIo() is renamed to runtime.blockingIo(), and all network transports, providers, and time/randomness functions now require an explicit IO instance from the caller. All tests, examples, and documentation are updated accordingly.

Changes

Explicit IO Threading Throughout eth.zig

Layer / File(s) Summary
Runtime core refactoring
src/runtime.zig
Replaces defaultIo() with blockingIo() and refactors milliTimestamp() and sleepMs() to accept explicit io: std.Io parameters. Test coverage updated.
HTTP transport ownership
src/http_transport.zig
HttpTransport stores and exposes io: std.Io field. Constructor signature changes to init(allocator, url, io). Underlying std.http.Client uses provided io. Tests verify io threading.
SSE and WebSocket transports
src/sse_transport.zig, src/ws_transport.zig
sse_transport.subscribe* and WsTransport.connect* now accept explicit io parameter. Reconnect loops use io for sleep delays; deadline and ping-nonce generation use injected io.
WebSocket client keepalive and reconnection
src/ws_client.zig
WsClient stores io field; connect() signature adds io parameter. Keepalive timestamps, ping-nonce generation, and reconnect delays all use self.io instead of runtime defaults. Test stubs updated.
Provider ecosystem with shared IO
src/flashbots.zig, src/fallback_provider.zig, src/mev_share.zig
Relay.init, FallbackProvider.init, and MevShareClient.init/initMainnet accept explicit io. FallbackProvider stores io for shared health-tracking timestamps. All tests pass blockingIo().
Provider wrapper and dependent modules
src/provider.zig, src/retry_provider.zig, src/nonce_manager.zig, src/wallet.zig
Provider exposes new io() method. RetryingProvider uses inner.io() for PRNG seeding and backoff sleep. NonceManager and Wallet use provider.io() for lock/sleep timing. All tests construct real HttpTransport with blockingIo().
Entropy and encryption functions
src/mnemonic.zig, src/keystore.zig
mnemonic.generate(io, ...) and keystore.encrypt(..., io, ...) now require explicit io for entropy/randomness. All tests pass blockingIo().
Contract modules and multicall
src/contract.zig, src/erc20.zig, src/erc721.zig, src/multicall.zig
Add runtime imports and update test HttpTransport initialization to pass blockingIo(). No changes to exported ERC interfaces or ABI logic.
Integration tests and examples
tests/integration_tests.zig, examples/01_derive_address.zig, examples/02_check_balance.zig, examples/03_sign_message.zig, examples/04_send_transaction.zig, examples/05_read_erc20.zig, examples/06_hd_wallet.zig, examples/07_selectors.zig, examples/08_mev_share_backrunner.zig
All HTTP transport and WebSocket client initializations updated to pass eth.runtime.blockingIo(). Examples replace eth.runtime.defaultIo() with blockingIo() for stdout writers and transport initialization.
Documentation and changelog
CHANGELOG.md, docs/content/docs/*.mdx
Changelog documents breaking change that std.Io is now explicit across all network constructors and randomness/sleep functions. All example code snippets and constructor calls updated to show eth.runtime.blockingIo() parameter. New "Pluggable Io" section added to websockets guide explaining IO dependency injection.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • StrobeLabs/eth.zig#70: The main PR's changes to src/fallback_provider.zig introduce an io field and refactor FallbackProvider.init signature to accept explicit io, directly implementing the multi-RPC failover and per-endpoint health-tracking behavior described in this issue.

Possibly related PRs

  • StrobeLabs/eth.zig#48: The main PR's changes to src/sse_transport.zig thread explicit io through subscribe/subscribeWithReconnect, directly building on the SSE transport APIs added in this PR.
  • StrobeLabs/eth.zig#92: The main PR's IO plumbing updates to src/fallback_provider.zig (adding explicit io parameter/field and switching health timestamps to runtime.milliTimestamp(self.io)) directly modify the same failover/health-tracking logic introduced in this PR.
  • StrobeLabs/eth.zig#45: The main PR updates src/retry_provider.zig to thread explicit std.Io through PRNG seeding and backoff sleeping, directly modifying the retry middleware code introduced in this PR.

Poem

🐰 Hop, skip, jump—the IO threads through,
No global defaults, only what's due.
From transports to wallets, each function now knows,
Which context to use, as the refactor flows!
Dependency dance, explicit and bright—
eth.zig's plumbing is finally right! 🎯

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: making std.Io an explicit parameter instead of relying on a hidden global dependency.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/explicit-io

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

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
eth-zig Ready Ready Preview, Comment Jun 11, 2026 1:45pm

Request Review

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/content/docs/websockets.mdx (1)

13-14: ⚠️ Potential issue | 🟠 Major

Fix websocket docs: use WsTransport.connect(..., io) (the repo has no WebSocketTransport.init)

src/ws_transport.zig exports WsTransport with pub fn connect(allocator: std.mem.Allocator, url: []const u8, io: std.Io) ...; there is no WebSocketTransport or WebSocketTransport.init symbol in the codebase. The examples in docs/content/docs/websockets.mdx at lines 13-14 (also 26-27, 47-48) therefore won’t compile as written. Update the docs to call WsTransport.connect(allocator, url, io) (and adjust the module listing, e.g. docs/content/docs/modules.mdx line 102).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/content/docs/websockets.mdx` around lines 13 - 14, The docs call a
non-existent symbol WebSocketTransport.init; update the examples to use the real
exported API by replacing WebSocketTransport.init(...) calls with
WsTransport.connect(allocator, "wss://...", io) and ensure the third parameter
(io) is passed, then adjust any module listing that references
WebSocketTransport to reference WsTransport instead (matching the
implementation's pub fn connect signature).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/content/docs/websockets.mdx`:
- Around line 13-14: The docs call a non-existent symbol
WebSocketTransport.init; update the examples to use the real exported API by
replacing WebSocketTransport.init(...) calls with WsTransport.connect(allocator,
"wss://...", io) and ensure the third parameter (io) is passed, then adjust any
module listing that references WebSocketTransport to reference WsTransport
instead (matching the implementation's pub fn connect signature).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82428e73-8562-49c5-b3cd-bc73c1898be9

📥 Commits

Reviewing files that changed from the base of the PR and between 7127b0d and 4264168.

📒 Files selected for processing (38)
  • CHANGELOG.md
  • docs/content/docs/batch-calls.mdx
  • docs/content/docs/ens.mdx
  • docs/content/docs/examples.mdx
  • docs/content/docs/fallback-provider.mdx
  • docs/content/docs/introduction.mdx
  • docs/content/docs/keystore.mdx
  • docs/content/docs/mev-share.mdx
  • docs/content/docs/nonce-manager.mdx
  • docs/content/docs/transactions.mdx
  • docs/content/docs/websockets.mdx
  • examples/01_derive_address.zig
  • examples/02_check_balance.zig
  • examples/03_sign_message.zig
  • examples/04_send_transaction.zig
  • examples/05_read_erc20.zig
  • examples/06_hd_wallet.zig
  • examples/07_selectors.zig
  • examples/08_mev_share_backrunner.zig
  • src/contract.zig
  • src/erc20.zig
  • src/erc721.zig
  • src/fallback_provider.zig
  • src/flashbots.zig
  • src/http_transport.zig
  • src/keystore.zig
  • src/mev_share.zig
  • src/mnemonic.zig
  • src/multicall.zig
  • src/nonce_manager.zig
  • src/provider.zig
  • src/retry_provider.zig
  • src/runtime.zig
  • src/sse_transport.zig
  • src/wallet.zig
  • src/ws_client.zig
  • src/ws_transport.zig
  • tests/integration_tests.zig

@koko1123
koko1123 merged commit 32dae2e into main Jun 11, 2026
14 checks passed
@koko1123
koko1123 deleted the feat/explicit-io branch June 11, 2026 13:54
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