[FR] Add native Zig bindings via top-level `bindings/zig`

Open
#2,266 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
42/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet
Tech stack
cmake, cpp, github-actions, zig

Research direction

Start by comparing the existing Python and Rust binding layouts, then read the top-level CMakeLists.txt and the proposed bindings/zig files. Verify the C adapter, Zig API, build integration, and listed CI commands against the scoped APIs. Done means the 14 unit tests, seven examples, zig build test, and ctest -R zig_bindings_tests pass.

Written by the indexing model from the issue text.

Description

Problem Statement

As Zig gains traction in systems programming, an increasing number of polyglot codebases (C++ and Zig) require uniform performance tracking. Currently, organizations using google/benchmark across their C++ infrastructure lack a native way to harness the same execution engine, CLI flags (--benchmark_filter), and structured output formats (JSON/CSV) for their Zig services.

While Zig has excellent C interop, creating bindings against a pure C++ library requires a thin adapter layer. We want to explore adding official, native Zig bindings to google/benchmark without disrupting the existing C++ developer experience, build architecture, or performance characteristics.

Proposed Architecture

The Zig bindings are hosted co-located within the main repository under bindings/zig/, following the same pattern as existing Python and Rust bindings.

Repository Layout
google/benchmark/
├── CMakeLists.txt
├── src/
├── include/
└── bindings/
    ├── python/
    ├── rust/
    └── zig/
        ├── CMakeLists.txt
        ├── build.zig
        ├── build.zig.zon
        └── src/
            ├── zig_api.h        # C adapter header
            ├── zig_api.cc       # C adapter implementation
            ├── benchmark.zig    # Idiomatic Zig public API
            └── benchmark_test.zig
Build Integration
  • For Zig Users: build.zig invokes CMake to build libbenchmark + the C adapter together as a combined static archive, avoiding C++ ABI mismatches (libstdc++ vs libc++).
  • For C++ Users: An optional flag -DBENCHMARK_ENABLE_ZIG_BINDINGS=ON looks for zig and runs the Zig test suite during CI.
The Interop Layer

Since Zig has zero-cost C interop (no FFI bridge crate needed), we use a thin extern "C" adapter layer (zig_api.h/cc) that wraps C++ methods. Zig calls these via @cImport:

const c = @cImport(@cInclude("zig_api.h"));

// Benchmark registration with comptime trampoline
pub fn registerBenchmark(name: [*:0]const u8, comptime func: fn (*State) void) Benchmark {
    const S = struct {
        fn trampoline(state_ptr: ?*anyopaque) callconv(.c) void {
            if (state_ptr) |ptr| {
                var state = State{ .ptr = ptr };
                func(&state);
            }
        }
    };
    return Benchmark{ .ptr = c.benchmark_zig_register_benchmark(name, &S.trampoline) };
}

The comptime trampoline generates a unique static function per benchmark at compile time — zero heap allocation, zero dynamic dispatch.

Public Zig API
fn my_benchmark(state: *benchmark.State) void {
    while (state.keepRunning()) {
        // your code to benchmark
    }
}

pub fn main() void {
    const args = std.process.argsAlloc(std.heap.page_allocator) catch return;
    defer std.process.argsFree(std.heap.page_allocator, args);
    benchmark.initialize(args);
    _ = benchmark.registerBenchmark("BM_MyBenchmark", my_benchmark)
        .range(8, 1 << 20)
        .threads(4)
        .unit(.microsecond);
    _ = benchmark.run();
}

Key Design Decisions

  1. Opaque pointers (void*)State and Benchmark are passed as opaque void* through the C boundary. Zig wraps them in typed structs. This avoids fragile layout coupling to C++ internals.
  2. Comptime trampolines — Each registerBenchmark call generates a unique C-compatible callback at compile time, eliminating heap allocation and runtime dispatch.
  3. Combined static archive — The C adapter is compiled with the same g++ as libbenchmark via CMake, ensuring a single consistent C++ ABI (no libstdc++/libc++ conflicts).
  4. String convention — Zig uses [*:0]const u8 (sentinel-terminated) at the boundary, enforcing null-termination at compile time.

Scope

Covered:

  • Initialize, RunSpecifiedBenchmarks, RegisterBenchmark, ClearRegisteredBenchmarks, AddCustomContext
  • State: KeepRunning, KeepRunningBatch, PauseTiming, ResumeTiming, SkipWithError, SetBytesProcessed, SetItemsProcessed, SetLabel, SetComplexityN, range, iterations, threads, threadIndex
  • Benchmark builder: Arg, Range, DenseRange, Args, Unit, Threads, ThreadRange, MinTime, Iterations, Repetitions, UseRealTime, UseManualTime, Complexity
  • Enums: TimeUnit, BigO

Not covered (yet): ComputeStatistics, Fixture, ScopedPauseTiming, custom reporters.

Testing

  • 14 unit tests covering all bound APIs
  • 7 usage examples (basic, throughput, parameterized, threaded, pause/resume, skip, etc.)
  • CI integration via GitHub Actions (zig build test) and CMake (ctest -R zig_bindings_tests)

Open Questions

  1. Thread safety: Is the current approach (opaque void* + comptime trampolines) sufficient, or do we need to expose additional State/Benchmark internals for advanced use cases?
  2. Fixture support: Would Zig users benefit from a Fixture-like pattern (equivalent to C++ BENCHMARK_F)?

AI Usage

Code was generated with AI assistance and reviewed by the contributor (as per AGENTS.md).

Dominant language
C++
Stars
10.4k
Forks
1.8k
Avg merge
2d 4h
Merged PRs (30d)
8

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from google/benchmark

All issues in google/benchmark

Similar issues

More C++ issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.