{"id":18893209,"url":"https://github.com/ramonmeza/zig-c-tutorial","last_synced_at":"2025-08-20T12:14:46.765Z","repository":{"id":261542738,"uuid":"884617099","full_name":"ramonmeza/zig-c-tutorial","owner":"ramonmeza","description":"Learn to create Zig bindings for C libraries!","archived":false,"fork":false,"pushed_at":"2024-11-07T17:44:31.000Z","size":33,"stargazers_count":31,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-07-09T23:20:21.048Z","etag":null,"topics":["binding","c","compiling","linking","shared","static","wrapper","zig","zig-program"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ramonmeza.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-11-07T04:32:55.000Z","updated_at":"2025-07-02T09:59:52.000Z","dependencies_parsed_at":"2025-04-12T03:40:31.605Z","dependency_job_id":"f9d3f957-497c-4684-ad64-2ffbe24695d0","html_url":"https://github.com/ramonmeza/zig-c-tutorial","commit_stats":null,"previous_names":["ramonmeza/zig-c-tutorial"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ramonmeza/zig-c-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonmeza%2Fzig-c-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonmeza%2Fzig-c-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonmeza%2Fzig-c-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonmeza%2Fzig-c-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ramonmeza","download_url":"https://codeload.github.com/ramonmeza/zig-c-tutorial/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonmeza%2Fzig-c-tutorial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271314524,"owners_count":24738188,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-20T02:00:09.606Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binding","c","compiling","linking","shared","static","wrapper","zig","zig-program"],"created_at":"2024-11-08T08:12:28.052Z","updated_at":"2025-08-20T12:14:46.730Z","avatar_url":"https://github.com/ramonmeza.png","language":"Zig","readme":"# Understanding How Zig and C Interact\n\nA journey to understanding how Zig interacts with C and how someone not \nwell-versed in C can leverage the power of third-party C libraries.\n\n## What This Doesn't Cover\n\n- Using Zig in C\n- Using C\n- C at all\n\n## TOC\n\n- [Utilizing This Project](#utilizing-this-project)\n    - [Zig Build Commands](#zig-build-commands)\n- [The Journey](#the-journey)\n    - [Create a Simple C application](#create-a-simple-c-application)\n    - [Compiling the application using `zig cc`](#compiling-the-application-using-zig-cc)\n    - [Leverage Zig's build system to build the C application](#leverage-zigs-build-system-to-build-the-c-application)\n    - [Create a Zig application that links to the C library](#create-a-zig-application-that-links-to-the-c-library)\n    - [Using Zig to build a C Library](#using-zig-to-build-a-c-library)\n    - [Create a Zig wrapper around a C Function](#create-a-zig-wrapper-around-a-c-function)\n    - [Linking to the Static/Shared Library](#linking-to-the-staticshared-library)\n- [Side Quests](#side-quests)\n    - [Testing C code in Zig](#testing-c-code-in-zig)\n- [Resources](#resources)\n- [Thanks](#thanks)\n\n## Utilizing this Project\n\n### Zig Build Commands\n```sh\nzig build [steps] [options]\n\nSteps:\n  install (default)            Copy build artifacts to prefix path\n  uninstall                    Remove build artifacts from prefix path\n  c_app                        Run a C application built with Zig's build system.\n  zig_app                      Run a Zig application linked to C source code.\n  zmath_static                 Create a static library from C source code.\n  zmath_shared                 Create a shared library from C source code.\n  zig_app_shared               Run a Zig application that is linked to a shared library.\n  zig_app_static               Run a Zig application that is linked to a static library.\n  tests                        Run a Zig tests of C source code.\n```\n\n## The Journey\n### Create a Simple C Application\n\nLet's create a simple math library in C, with functions declared in a header \nfile and implemented in the source file.\n\n[`zmath.h`](include/zmath.h)\n```c\nextern int add(int a, int b);\nextern int sub(int a, int b);\n```\n_Note: `extern` is used here to export our functions._\n\n[`zmath.c`](src/zmath.c)\n```c\n#include \"zmath.h\"\n\nint add(int a, int b) {\n    return a + b;\n}\n\nint sub(int a, int b) {\n    return a - b;\n}\n\n```\n\nNext we create a simple application to utilize this library.\n\n[`c_app.c`](src/c_app.c)\n```c\n#include \u003cstdio.h\u003e\n#include \"zmath.h\"\n\nint main(void) {\n    int a = 10;\n    int b = 5;\n    \n    int resultAdd = add(a, b);\n    printf(\"%d + %d = %d\\n\", a, b, resultAdd);\n    \n    int resultSub = sub(a, b);\n    printf(\"%d - %d = %d\\n\", a, b, resultSub);\n\n    return 0;\n}\n```\n\n### Compiling the application using `zig cc`\n\nIf you're familiar with `gcc`, this is no problem. Here's the command to compile \nthis application:\n\n```sh\nzig cc -Iinclude src/c_app.c src/zmath.c -o zig-out/bin/c_app.exe\n```\n_The nice part about Zig is that it's a cross-compiler, so feel free to ignore that I'm on Windows._\n\nNow run the resulting executable:\n\n```sh\n\u003e ./zig-out/bin/c_app.exe\n10 + 5 = 15\n10 - 5 = 5\n```\n\n### Leverage Zig's build system to build the C application\n\nNow we can create a file called `build.zig`, which Zig will use to build our application.\n\n[`build.zig`](build_c_app.zig)\n```c\nconst std = @import(\"std\");\n\npub fn build(b: *std.Build) void {\n    const target = b.standardTargetOptions(.{});\n    const optimize = b.standardOptimizeOption(.{});\n\n    const exe = b.addExecutable(.{\n        .name = \"c_app\",\n        .target = target,\n        .optimize = optimize,\n    });\n\n    exe.addIncludePath(b.path(\"include\"));\n    exe.addCSourceFiles(.{\n        .files = \u0026[_][]const u8{\n            \"src/main.c\",\n            \"src/zmath.c\"\n        }\n    });\n\n    exe.linkLibC();\n\n    b.installArtifact(exe);\n}\n```\n\nThings to note:\n- `b.addExecutable()` allows us to create an executable with given options, in \nthis case default target options and optimizations. It also allows us to name \nour executable.\n- `exe.addIncludePath()` takes a `LazyPath`, and it just so happens that \n`std.Build` (the type of `b`) contains a function to return a `LazyPath` object \ngiven a string.\n- `exe.addCSourceFiles()` takes a `struct` that includes a `files` property. \nThis property is a pointer to an array of strings. I'll break down \n`\u0026[_][]const u8 {}` real quick in plain English:\n\n```c\n\u0026[_][]const u8 {\n    \"..\",\n    \"...\",\n    // etc\n}\n\n// const u8 is a character\n// []const u8 is an array of characters, or a string\n// [_] is zig for \"create an array with size automatically determined at compile time\"\n// [_][]const u8 is an array of strings\n// \u0026 gives us a pointer to an object's memory address\n// \u0026[_][]const u8 is a pointer to an array of strings\n\n\n// a reference to an automatically sized array of array of constants u8 objects.\n// a pointer to the address in memory where an array of arrays of u8 exists\n```\n\n_Probably overkill of an explanation, but maybe someone will benefit from this._\n\n- Next is `exe.linkLibC()`. This is the extremely convenient way that Zig links \nto libc. There's also `linkLibCpp()`, which could be useful to keep in mind. If \nyou use a standard library, such as `stdio.h`, make sure to include `linkLibC()`, \notherwise you'll get a compilation error when trying to build.\n\nNow we kick off the Zig build process:\n\n```sh\nzig build\n```\n\nAnd run the resulting executable:\n\n```sh\n./zig-out/bin/c_compiled_with_zig_build.exe\n10 + 5 = 15\n10 - 5 = 5\n```\n\nSame results as compiling with `zig cc`! Very cool. Let's move on to using a bit more Zig.\n\n### Create a Zig application that links to the C library\n\nBasically, we want to recreate `c_app.c` in Zig. In this case, this is trivial.\n\n[`zig_app.zig`](src/zig_app.zig)\n```c\nconst std = @import(\"std\");\nconst zmath = @cImport(@cInclude(\"zmath.h\"));\n\npub fn main() !void {\n    const stdio = std.io.getStdOut().writer();\n\n    const a = 10;\n    const b = 5;\n\n    const resultAdd = zmath.add(a, b);\n    try stdio.print(\"{} + {} = {}\\n\", .{ a, b, resultAdd });\n\n    const resultSub = zmath.sub(a, b);\n    try stdio.print(\"{} - {} = {}\\n\", .{ a, b, resultSub });\n}\n```\n\nThis is fairly simple to understand. What's important is that we are including \nour C headers using Zig's `@cImport()` and `@cInclude()`.\n\nNext we have to modify `build.zig` and point it to our `zig_app.zig` file \ninstead of `c_app.c`.\n\n[`build.zig`](build_zig_app.zig)\n```c\nconst std = @import(\"std\");\n\npub fn build(b: *std.Build) void {\n    const target = b.standardTargetOptions(.{});\n    const optimize = b.standardOptimizeOption(.{});\n\n    const exe = b.addExecutable(.{\n        .name = \"build_zig_linked_to_c\",\n        .root_source_file = b.path(\"src/zig_app.zig\"),\n        .target = target,\n        .optimize = optimize,\n    });\n\n    exe.addIncludePath(b.path(\"include\"));\n    exe.addCSourceFile(.{\n        .file = b.path(\"src/zmath.c\"),\n    });\n\n    exe.linkLibC();\n\n    return exe;\n}\n```\n\nThe only thing to pay attention to here is the `root_source_file` parameter in \n`addExecutable()`. Here we point to a Zig file with `pub fn main() void` \nimplemented. This is a drop in replacement of `c_app.c`, or rather a Zig file \nusing a C library (when source code is available).\n\n\n### Using Zig to build a C Library\n\nUp until now, we've been utilizing the C source code, since it's available to us, \nbut this is not always the case. Sometimes we may have a static or shared library \nthat we need to link against, rather than compiling the source code ourselves. \n \n_If you want a deeper understanding of static and shared libraries, check out \nthe links in the [Resources](#resources) section._\n\n[`build.zig`](build_c_static_lib.zig)\n```c\nconst std = @import(\"std\");\n\npub fn build(b: *std.Build) *std.Build.Step.Compile {\n    const target = b.standardTargetOptions(.{});\n    const optimize = b.standardOptimizeOption(.{});\n\n    const lib = b.addStaticLibrary(.{\n        .name = \"zmath_static\",\n        .target = target,\n        .optimize = optimize,\n    });\n\n    lib.addIncludePath(b.path(\"include\"));\n    lib.addCSourceFiles(.{ .files = \u0026[_][]const u8{\"src/zmath.c\"} });\n\n    lib.linkLibC();\n\n    b.installArtifact(lib);\n}\n```\n\nWe add our source files, include directory, link it to libc and install it. \nWhen we run `zig build`, our library will be compiled to a static library file, \n`zig-out/lib/zmath-static.lib`.\n\nIf you want to compile shared libraries instead, there's not much difference. \nInstead of `b.addStaticLibrary()`, use `b.addSharedLibrary()`.\n\n_[See the difference in build.zig](build_c_shared_lib.zig)_\n\n### Create a Zig wrapper around a C Function\n\nThis is a simple example, so wrapping the C function in Zig may be overkill. \nEither way, the idea is to wrap the call to our C functions in Zig, such that we \nhave a Zig interface between our application code and our C code. This allows us \nto handle errors in a Zig fashion and pass proper types to the C code while \nexposing them to the application code.\n\nFor this we'll create a new Zig file, `zmath_ext.zig`\n\n[`zmath_ext.zig`](src/zmath_ext.zig)\n```c\nconst zmath_h = @cImport(@cInclude(\"zmath.h\"));\n\npub extern fn add(a: c_int, b: c_int) callconv(.C) c_int;\npub extern fn sub(a: c_int, b: c_int) callconv(.C) c_int;\n```\n\nThis file is a declaration of external functions we wish to use in Zig. We'll \nnext create a `zmath.zig`, in which we will create Zig functions that will \nexpose Zig data types through our API and cast the parameters to their \ncorresponding C data types before calling the C functions.\n\n[`zmath.zig`](src/zmath.zig)\n```c\nconst zmath_ext = @import(\"zmath_ext.zig\");\n\npub fn add(a: i32, b: i32) !i32 {\n    const x = @as(c_int, @intCast(a));\n    const y = @as(c_int, @intCast(b));\n    return zmath_ext.add(x, y);\n}\n\npub fn sub(a: i32, b: i32) !i32 {\n    const x = @as(c_int, @intCast(a));\n    const y = @as(c_int, @intCast(b));\n    return zmath_ext.sub(x, y);\n}\n```\n\nAs you can see, we translate the C types to Zig specific types for use in Zig \napplications. We cast our input parameters to their C equivalent (`c_int`) for \nthe C function's parameters. You'll also notice the return type contains `!`, \nmeaning these functions will now return errors. This means within our \napplication, we'll need to call the function with `try`.\n\nWe'll create a new zig file for trying out the wrapper functions, called \n`zig_c_wrapper.zig`. This is mostly to distinguish between our previous examples, \nbut this is just showing we no longer use `@cImport()` directly, and instead \nutilize `zmath.zig` (our wrapper functions), to interact with the C code.\n\n[`zig_c_wrapper.zig`](src/zig_c_wrapper.zig)\n```c\nconst std = @import(\"std\");\nconst zmath = @import(\"zmath.zig\");\n\npub fn main() !void {\n    const stdio = std.io.getStdOut().writer();\n\n    const a = 10;\n    const b = 5;\n\n    const resultAdd = try zmath.add(a, b);\n    try stdio.print(\"{d} + {d} = {d}\\n\", .{ a, b, resultAdd });\n\n    const resultSub = try zmath.sub(a, b);\n    try stdio.print(\"{d} - {d} = {d}\\n\", .{ a, b, resultSub });\n}\n```\n\n### Linking to the Static/Shared Library\n\nWith this in place, and our static/shared library created, we can use `build.zig` \nto link our application to our library.\n\n[`build.zig` for static libray](build_zig_app_static.zig)\n```c\nconst std = @import(\"std\");\n\npub fn build(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Step.Compile {\n    const exe = b.addExecutable(.{\n        .name = \"zig_app_shared\",\n        .root_source_file = b.path(\"src/zig_c_wrapper.zig\"),\n        .target = target,\n        .optimize = optimize,\n    });\n\n    exe.addObjectFile(b.path(\"zig-out/lib/zmath-shared.lib\"));\n\n    return exe;\n}\n```\n\nIt would be wise to note that here we built our library and then import it from \na path (using `addObjectFile()`). If you build from source, use `addObject(*Compile)` \ninstead and pass in the proper object. This is because Zig will compile faster \nthan your OS can save the library file, causing the build to fail because the \nlibrary file could not be found during the build time of this object (at least \nwhen using `dependsOn()`, like we do in the main [`build.zig`](build.zig) file \nfor this repo).\n\n\n## Side Quests\n\nSome extra thoughts about working with Zig and intergrating/interacting with C.\n\n### Testing C code in Zig\n\nI was curious if I could use Zig's testing features to test C code. There's \nliterally no reason why you couldn't, so here's how you do it.\n\n[`test_zmath.zig`](tests/test_zmath.zig)\n```c\nconst std = @import(\"std\");\nconst testing = std.testing;\n\nconst zmath = @cImport(@cInclude(\"zmath.h\"));\n\ntest \"zmath.add() works\" {\n    try testing.expect(zmath.add(1, 2) == 3);\n    try testing.expect(zmath.add(12, 12) == 24);\n}\n\ntest \"zmath.sub() works\" {\n    try testing.expect(zmath.sub(2, 1) == 1);\n    try testing.expect(zmath.sub(12, 12) == 0);\n}\n```\n_Strive to write good tests, this is just a proof of concept._\n\n[`build.zig`](build_test_zmath.zig)\n```c\nconst std = @import(\"std\");\n\npub fn build(b: *std.Build) void {\n    const target = b.standardTargetOptions(.{});\n    const optimize = b.standardOptimizeOption(.{});\n\n    const tests_exe = b.addTest(.{\n        .name = \"test_zmath\",\n        .root_source_file = b.path(\"tests/test_zmath.zig\"),\n        .target = target,\n        .optimize = optimize,\n    });\n\n    tests_exe.addIncludePath(b.path(\"include\"));\n    tests_exe.addCSourceFile(.{\n        .file = b.path(\"src/zmath.c\"),\n    });\n\n    tests_exe.linkLibC();\n\n    b.installArtifact(tests_exe);\n}\n```\n\n## Resources\n- https://mtlynch.io/notes/zig-call-c-simple/\nWhat initially made me want to tackle this subject, this article is a great \nstarting point for understanding C and Zig.\n- [Wikipedia article for \"Shared Library\"](https://en.wikipedia.org/wiki/Shared_library)\n- [Wikipedia article for \"Static Library\"](https://en.wikipedia.org/wiki/Static_library)\n- [Discussion about this repository on Ziggit](https://ziggit.dev/t/blog-understanding-how-zig-and-c-interact/6733/7)\n\n\n\n## Thanks\n\nThat's it. It was a long journey, but one that came with lots of learning and \nexperimenting. Zig is wonderful and being able to use C code without having to \nuse C is a game changer for me.\n\nThanks for sticking around and reading. If you have feedback or suggestions, \nplease don't hesitate to create an issue and we can work through it together.\n\n- Ramon\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Framonmeza%2Fzig-c-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Framonmeza%2Fzig-c-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Framonmeza%2Fzig-c-tutorial/lists"}