Blessed Packages
Zig++ blesses one way to use a C library from Zig++: fetch the library at a pinned commit, build it with its own build script, translate its headers with translate-c, and put a Zig face on the translation. A blessed package is a package made that way, which Zig++ maintains and tests. The first is raylibz.
raylibz
raylibz is the Zig++-sanctioned wrapper for raylib, the library for making games by Ramon Santamaria (raysan). Thank you to raysan and raylib’s contributors, who have given raylib away under the zlib license since 2013. If raylibz gets you a window, please consider sponsoring raysan.
raylibz is a thin, one-to-one wrapper. Every function of raylib.h has
raylib’s name in Zig case, InitWindow is initWindow, and raymath’s
functions are methods on the vector and matrix types: Vector2Add(a, b) is
a.add(b). The C declarations it wraps are raylib’s own translation, and they
are always there as raylibz.c. raylib’s
cheatsheet is raylibz’s
documentation too.
A window, from nothing
These steps take a machine with nothing on it to a raylib window, on Linux, macOS or Windows.
-
Install Zig++. On Linux and macOS:
curl -fsSL https://zigpp.lol/ppup | shOn Windows, in PowerShell:
irm https://zigpp.lol/ppup.ps1 | iexThen open a new terminal, so that
zigis on yourPATH. See Installing. -
On Linux, install the headers raylib’s window layer builds against. On Debian and Ubuntu:
sudo apt install libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxext-dev libxfixes-dev libgl-devmacOS and Windows need nothing more.
-
Make a project.
mkdir hello-raylib cd hello-raylib zig initzig initwritesbuild.zig,build.zig.zonandsrc/. Thebuild.zig.zonit writes pins the Zig++ build that made it, inminimum_zig_version, so the project keeps building with that build whicheverzigruns it. See Any Zig Version. -
Fetch raylibz, pinned to a commit. raylibz has no tags. Pick a commit; the newest on main is a good one:
zig fetch --save https://github.com/mattneel/raylibz/archive/bc0ec551a3e7de06739c60dd67b1760e9f432870.tar.gzThat records the URL, and the hash of what it downloaded, in
build.zig.zonunder the nameraylibz. -
Give your program the module. In
build.zig, after the line that startsconst exe = b.addExecutable(, and after the call it opens has closed, add:const raylibz = b.dependency("raylibz", .{ .target = target, .optimize = optimize }); exe.root_module.addImport("raylibz", raylibz.module("raylibz")); -
Write the program. Replace everything in
src/main.zigwith:const rl = @import("raylibz"); pub fn main() void { rl.initWindow(800, 450, "hello, raylib"); defer rl.closeWindow(); rl.setTargetFPS(60); while (!rl.windowShouldClose()) { rl.beginDrawing(); defer rl.endDrawing(); rl.clearBackground(rl.Color.raywhite); rl.drawText("Congrats! You created your first window!", 190, 200, 20, rl.Color.lightgray); } } -
Run it.
zig build runThe first build takes a minute or more, because it compiles Zig++’s build runner and raylib. A window opens; close it, or press Escape.
What just happened
zig fetchdownloaded raylibz.zig buildthen fetched raylibz’s own dependency, raylib: mattneel/raylib, raysan’s raylib with its build scripts ported to Zig++’s build API and one fix in the gamepad library it vendors, following raylib’s master.- raylib’s own
build.zigcompiled raylib’s C with the Clang inside Zig++, and chose raylib’s window layer: GLFW, on X11 on Linux. Every one of raylib’s build options passes through raylibz unchanged: add.platform = .rgfwto theb.dependencycall from step 5, and the program runs on RGFW instead. - The same build script ran translate-c over
raylib.h,raymath.handrlgl.h. Zig++’s translator reads C headers and writes Zig declarations for them, and raylib’s build script publishes those as modules. - raylibz imported the translations and put the Zig face on them:
initWindowtakes a string literal as it is,Color.raywhiteis a Zig value, andloadTexturereturnserror{LoadFailed}!Texture2D, where C returns a texture you must remember to check. raylibz’s tests prove the face against the translation when it compiles: every struct’s layout, every enum value, and that no raylib function is missing. - raylib’s own declarations stay one step away:
rl.c.InitWindow(800, 450, "hello")calls the translation directly.
Your own C library
A library whose build script does not translate its headers gets the same
treatment from your build.zig:
const mylib_h = b.addTranslateC(.{
.root_source_file = b.path("vendor/mylib/include/mylib.h"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("mylib", mylib_h.createModule());
@import("mylib") is then the translated header. Link the library itself as
its build script produces it, for example with exe.root_module.linkLibrary
on the artifact of its package, and wrap what you use. raylibz’s
source shows each of its rules at work.