A Simple Example of Calling a C Library from Zig

I’m a newcomer to Zig, but I met Andrew Kelly and several other Zig developers at Handmade Seattle this week, and it motivated me to start experimenting with the language.

I had a hard time finding simple, complete documentation about how to call C code from Zig. I pieced together a solution from a few different sources and then documented what I did:

Any feedback is welcome!

9 Likes

Great article! I think this will be helpful to other newcomers.
There is one small detail that I’d like to add:

This article showed the simplest example I could think of for showing how to call C code from Zig.

There actually is an even simpler way. You don’t need to add a static library, you can also directly add the c code to the executable:

exe.linkLibC();
exe.addCSourceFiles(...);

But you’d obviously need to this for the unit test step as well.
So if you use unit-testing(and the tests require the C library), making a static lib is the better solution I guess.

2 Likes

If the goal is to be as simple as possible, I suggest you can even have no C header file. pgcd.c:

unsigned int
pgcd(unsigned int l, unsigned int r)
{
    while (l != r) {
        if (l > r) {
            l = l - r;
        } else {
            r = r - l;
        }
    }
    return l;
}

Now, use-pgcd.zig:

const std = @import("std");
const c = @cImport(@cInclude("pgcd.c")); // No need to have a .h

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);
    if (args.len != 3) {
        std.debug.print("Usage: find-pgcd L R\n", .{});
        return error.WrongArgs;
    }
    var l = try std.fmt.parseInt(u32, args[1], 10);
    var r = try std.fmt.parseInt(u32, args[2], 10);
    // A good reading is https://renato.athaydes.com/posts/testing-building-c-with-zig.html
    std.debug.print("PGCD({d},{d}) = {d}\n", .{ l, r, c.pgcd(l, r) });
}

const expect = @import("std").testing.expect;

fn expectidentical(i: u32) !void {
    try expect(c.pgcd(i, i) == i);
}

test "identical" {
    try expectidentical(1);
    try expectidentical(7);
    try expectidentical(18);
}

test "primes" {
    try expect(c.pgcd(4, 13) == 1);
}

test "pgcdexists" {
    try expect(c.pgcd(15, 35) == 5);
}

test "pgcdlower" {
    try expect(c.pgcd(15, 5) == 5);
}

And compile with:

% zig build-exe use-pgcd.zig -lc -I.

% ./use-pgcd 36 8
PGCD(36,8) = 4

Or if you prefer a build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const exe = b.addExecutable(.{
        .name = "find-pgcd",
        .root_source_file = .{ .path = "use-pgcd.zig" },
    });
    exe.addIncludePath(.{ .path = "." });
    b.installArtifact(exe);
    const lib = b.addStaticLibrary(.{
        .name = "pgcd",
        .root_source_file = .{ .path = "pgcd.c" },
        .target = .{},
        .optimize = std.builtin.OptimizeMode.Debug,
    });
    exe.linkLibrary(lib);
}

Thanks! That post is really helpful!

I’ll experiment with importing the .c file directly and update the post.

1 Like

Thanks @IntegratedQuantum and @bortzmeyer! Including the .c file directly simplifies things a lot. I’ve updated the post and credited you both for the help!

2 Likes

Thank you! I love simple/minimal examples for understanding new concepts!

2 Likes