Zenoh in Zig, how to interoperate with Rust? Via C FFI?

I want to use zenoh in my zig application. Zenoh is a distributed messaging middleware written in rust, but they provide a c binding.

How should I approach this? Should I write zig bindings for the c bindings?

Does anyone have examples of zig calling a rust library?

Looks like they provide pre-compiled binaries, perhaps for now I can link to those and download them using the zig package manager

I also found this great resource on c-interop: GitHub - ramonmeza/zig-c-tutorial: Learn to create Zig bindings for C libraries!

Not sure if it helps, but GitHub - akarpovskii/build.crab: Use Rust libraries in Zig might be interesting.

1 Like

I gave up on using the rust builder thing. The project publishes the pre-built c static libraries already, so I just depended on each one individually for each target.

build.zig

    const zenoh_c_dep = switch (target.result.cpu.arch) {
        .x86_64 => switch (target.result.os.tag) {
            .windows => switch (target.result.abi) {
                .gnu => b.dependency("zenoh_c_x86_64_windows_gnu", .{}),
                .msvc => b.dependency("zenoh_c_x86_64_windows_msvc", .{}),
                else => @panic("unsupported target"),
            },
            .linux => switch (target.result.abi) {
                .musl => b.dependency("zenoh_c_x86_64_linux_musl", .{}),
                .gnu => b.dependency("zenoh_c_x86_64_linux_gnu", .{}),
                else => @panic("unsupported target"),
            },
            .macos => switch (target.result.abi) {
                .none => b.dependency("zenoh_c_x86_64_macos_none", .{}),
                else => @panic("unsupported target"),
            },
            else => @panic("unsupported target"),
        },
        .aarch64 => switch (target.result.os.tag) {
            .linux => switch (target.result.abi) {
                .musl => b.dependency("zenoh_c_aarch64_linux_musl", .{}),
                .gnu => b.dependency("zenoh_c_aarch64_linux_gnu", .{}),
                else => @panic("unsupported target"),
            },
            .macos => switch (target.result.abi) {
                .none => b.dependency("zenoh_c_aarch64_macos_none", .{}),
                else => @panic("unsupported target"),
            },
            else => @panic("unsupported target"),
        },
        else => @panic("unsupported target"),
    };

    const zenoh_c_static_lib_path = switch (target.result.os.tag) {
        .linux, .macos => zenoh_c_dep.path("lib/libzenohc.a"),
        .windows => zenoh_c_dep.path("lib/libzenohc.lib"),
        else => @panic("unsupported target"),
    };

    lib_mod.addObjectFile(zenoh_c_static_lib_path);
    lib_mod.addIncludePath(zenoh_c_dep.path("include"));

build.zig.zon

.dependencies = .{
        // TODO: add more?
        // linux
        .zenoh_c_aarch64_linux_musl = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-aarch64-unknown-linux-musl-standalone.zip",
            .hash = "N-V-__8AAOokVgO0AcAYT3WdXB_sFWghwdMVE1Z291qyaMwo",
        },
        .zenoh_c_x86_64_linux_musl = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-x86_64-unknown-linux-musl-standalone.zip",
            .hash = "N-V-__8AAON7RAPy6inzZoTLfrfM3qGOKhd6eyUPC_Kbw4A-",
        },
        .zenoh_c_aarch64_linux_gnu = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-aarch64-unknown-linux-gnu-standalone.zip",
            .hash = "N-V-__8AANDAegNVrxYqMADyiBUfo_EI-3CzAiLM-czQ7BZM",
        },
        .zenoh_c_x86_64_linux_gnu = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-x86_64-unknown-linux-gnu-standalone.zip",
            .hash = "N-V-__8AACUOjgMMalZq-rCJblTIgoqNKa_hFtph76nDBN1D",
        },
        // windows
        .zenoh_c_x86_64_windows_gnu = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-x86_64-pc-windows-gnu-standalone.zip",
            .hash = "N-V-__8AAOa-oAKhmKFDIrJU0MUdobrikyeUbFne6nbGYHlg",
        },
        .zenoh_c_x86_64_windows_msvc = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-x86_64-pc-windows-msvc-standalone.zip",
            .hash = "N-V-__8AAGaRNwKiaiEEwbjc6ro8FQQOB74sN5uJzkiRg7iR",
        },
        // macos
        .zenoh_c_aarch64_macos_none = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-aarch64-apple-darwin-standalone.zip",
            .hash = "N-V-__8AAPl_DwJ_CTIyx3fqzDbACK6lAIHEzA0MOu4oWYzR",
        },
        .zenoh_c_x86_64_macos_none = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-x86_64-apple-darwin-standalone.zip",
            .hash = "N-V-__8AACD4KgJzEvAkf4M8YcDrmb5pvDQiVnNRY-S1BdbV",
        },
    },

You should make those dependencies lazy so they are only fetched when actually used.
.lazy = true for each in the zon and change b.dependency to b.lazyDependency

2 Likes

Thanks for the tip!

Here’s what I did:

build.zig.zon

        .zenoh_c_aarch64_linux_musl = .{
            .url = "https://github.com/eclipse-zenoh/zenoh-c/releases/download/1.2.1/zenoh-c-1.2.1-aarch64-unknown-linux-musl-standalone.zip",
            .hash = "N-V-__8AAOokVgO0AcAYT3WdXB_sFWghwdMVE1Z291qyaMwo",
            .lazy = true,
        },

build.zig

    const zenoh_c_dep = switch (target.result.cpu.arch) {
        .x86_64 => switch (target.result.os.tag) {
            .windows => switch (target.result.abi) {
                .gnu => b.lazyDependency("zenoh_c_x86_64_windows_gnu", .{}),
                .msvc => b.lazyDependency("zenoh_c_x86_64_windows_msvc", .{}),
                else => @panic("unsupported target"),
            },
            .linux => switch (target.result.abi) {
                .musl => b.lazyDependency("zenoh_c_x86_64_linux_musl", .{}),
                .gnu => b.lazyDependency("zenoh_c_x86_64_linux_gnu", .{}),
                else => @panic("unsupported target"),
            },
            .macos => switch (target.result.abi) {
                .none => b.lazyDependency("zenoh_c_x86_64_macos_none", .{}),
                else => @panic("unsupported target"),
            },
            else => @panic("unsupported target"),
        },
        .aarch64 => switch (target.result.os.tag) {
            .linux => switch (target.result.abi) {
                .musl => b.lazyDependency("zenoh_c_aarch64_linux_musl", .{}),
                .gnu => b.lazyDependency("zenoh_c_aarch64_linux_gnu", .{}),
                else => @panic("unsupported target"),
            },
            .macos => switch (target.result.abi) {
                .none => b.lazyDependency("zenoh_c_aarch64_macos_none", .{}),
                else => @panic("unsupported target"),
            },
            else => @panic("unsupported target"),
        },
        else => @panic("unsupported target"),
    } orelse return;

    const zenoh_c_static_lib_path = switch (target.result.os.tag) {
        .linux, .macos => zenoh_c_dep.path("lib/libzenohc.a"),
        .windows => zenoh_c_dep.path("lib/libzenohc.lib"),
        else => @panic("unsupported target"),
    };

    zenoh.addObjectFile(zenoh_c_static_lib_path);
    zenoh.addIncludePath(zenoh_c_dep.path("include"));

    const lib_unit_tests = b.addTest(.{
        .root_module = zenoh,
    });
    lib_unit_tests.linkLibCpp();
    const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_lib_unit_tests.step);

Since I only really have one dependency from the set of lazy dependencies, I just put orelse return after switching on the architectures.

2 Likes