Thread panic when returning an error after initializing GLFW

Hi, I ran into an issue using GLFW with Zig 0.16.0 on Gentoo (Wayland/niri).

I first hit this in a bigger project, where I could open a window and render OpenGL quads fine, so GLFW itself is working. The panic only shows up when an error propagates out of main unhandled. Here’s the smallest repro I could get it down to:

main.zig

const std = @import("std");
const c = @cImport({
    @cInclude("GLFW/glfw3.h");
});

pub fn main(init: std.process.Init) !void {
    _ = init;
    if (c.glfwInit() == 0) return error.GlfwInitFailed;
    defer c.glfwTerminate();

    return error.DeliberateTestError;
}

build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "game",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .link_libc = true,
        }),
    });

    exe.root_module.linkSystemLibrary("glfw", .{});

    b.installArtifact(exe);

    const run_step = b.step("run", "Run the application");
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);

    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }
}

Here is the command I used to run the program:

$ zig build run
thread 19458 panic: attempt to use null value
error return context:

Then it just hangs, has to be killed.

If I remove the return error.DeliberateTestError; line so main exits normally, no problem at all. If I keep the error but don’t call glfwInit(), also no problem and it prints the error trace and exits. So glfwInit() specifically seems to be the thing that triggers it, not window/context creation or anything that comes normally after.

Backtrace from gdb:

$ gdb --args zig-out/bin/game 
GNU gdb (Gentoo 17.2 vanilla) 17.2
Copyright (C) 2025 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://bugs.gentoo.org/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from zig-out/bin/game...
(gdb) run
Starting program: /home/thomas/Projects/Games/balian/zig-out/bin/game 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
[New Thread 0x7ffff5f016c0 (LWP 19707)]
[New Thread 0x7ffff57006c0 (LWP 19708)]
[New Thread 0x7ffff4eff6c0 (LWP 19709)]
[New Thread 0x7fffe7fff6c0 (LWP 19710)]
[New Thread 0x7fffe77fe6c0 (LWP 19711)]
thread 19703 panic: attempt to use null value
error return context:
^C
Thread 1 "game" received signal SIGINT, Interrupt.
0x00000000010db8eb in os.linux.x86_64.syscall4 (number=futex, arg1=19318312, arg2=128, arg3=2, arg4=0)
(gdb) bt
#0  0x00000000010db8eb in os.linux.x86_64.syscall4 ()
#1  0x00000000010db4cf in os.linux.futex_4arg ()
#2  0x00000000010dab54 in Io.Threaded.Thread.futexWaitInner ()
#3  0x00000000010d890f in Io.Threaded.Thread.futexWaitUncancelable ()
#4  0x00000000010d87f7 in Io.Threaded.mutexLock ()
#5  0x00000000010d590d in Io.Threaded.scanEnviron ()
#6  0x00000000010d5a15 in Io.Threaded.environString__anon_13417 ()
#7  0x00000000010d0734 in debug.ElfFile.DebugInfoSearchPaths.native ()
#8  0x00000000010bddee in debug.SelfInfo.Elf.Module.loadElf ()
#9  0x00000000010bd694 in debug.SelfInfo.Elf.Module.getLoadedElf ()
#10 0x000000000103dfa1 in debug.SelfInfo.Elf.getSymbols ()
#11 0x00000000010272cf in debug.printSourceAtAddress ()
#12 0x00000000011241d1 in debug.writeTrace ()
#13 0x0000000001123c89 in debug.writeErrorReturnTrace ()
#14 0x000000000102521f in debug.defaultPanic ()
#15 0x000000000102ffa4 in debug.FullPanic((function 'defaultPanic')).unwrapNull ()
#16 0x00000000010d5ad6 in Io.Threaded.Environ.scan ()
#17 0x00000000010d597f in Io.Threaded.scanEnviron ()
#18 0x0000000001164c30 in Io.Threaded.initLockedStderr ()
#19 0x00000000011653fc in Io.Threaded.lockStderr ()
#20 0x00000000011263ff in Io.lockStderr ()
#21 0x00000000011260c5 in debug.lockStderr ()
#22 0x00000000011d25d5 in log.defaultLog__anon_35555 ()
#23 0x00000000011d24cb in log.log__anon_35548 ()
#24 0x00000000011d246b in log.scoped(.default).err__anon_32808 ()
#25 0x00000000011ccb51 in start.main ()
#26 0x00007ffff7cd21fe in ??? () at /usr/lib64/libc.so.6
#27 0x00007ffff7cd231b in __libc_start_main () at /usr/lib64/libc.so.6
#28 0x00000000012042b5 in _start ()
(gdb) 

I also tested with the official zig-x86_64-linux-0.16.0 binary from ziglang.org (not just my Gentoo-built one), same exact panic and backtrace, so it doesn’t look like a packaging issue on my end.

Let me know if there’s anything else I can test. Thanks in advance!

Copy-pasted your exact code, though did add exe.use_llvm = true; in build.zig (there is some issue when linking with libc).

Running on Arch/Hyprland (Zig 0.16) works as expected.

❯ zig build run
error: DeliberateTestError
/home/eric/zig/temp/src/main.zig:11:5: 0x11725d5 in main (game)
    return error.DeliberateTestError;
    ^
run
└─ run exe game failure
error: process exited with error code 1
failed command: /home/eric/zig/temp/zig-out/bin/game

Build Summary: 3/5 steps succeeded (1 failed)
run transitive failure
└─ run exe game failure

error: the following build command failed with exit code 1:
.zig-cache/o/a2d9c775799e82cbd685e268d70660c6/build /home/eric/.local/share/zvm/0.16.0/zig /home/eric/.l
ocal/share/zvm/0.16.0/lib /home/eric/zig/temp .zig-cache /home/eric/.cache/zig --seed 0x80ddb840 -Zdf98d
c787cea2bd3 run

It really doesn’t appear to be anything with your code, nothing amiss there aside from using the deprecated @cImport.

1 Like

Thanks for your reply! I was not aware that @cImport was deprecated.

Adding exe.root_module.use_llvm = true did not fix it for me.

I tried to use the new translate_c:

build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const translate_c = b.addTranslateC(.{
        .root_source_file = b.path("vendors/c.h"),
        .target = target,
        .optimize = optimize,
        // .link_libc = true,
    });
    translate_c.linkSystemLibrary("glfw", .{});

    const exe = b.addExecutable(.{
        .name = "game",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .imports = &.{
                .{
                    .name = "c",
                    .module = translate_c.createModule(),
                },
            },
        }),
    });
    // exe.use_llvm = true;
    b.installArtifact(exe);

    const run_step = b.step("run", "Run the application");
    const run_cmd = b.addRunArtifact(exe);
    run_step.dependOn(&run_cmd.step);

    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }
}

c.h

#include <GLFW/glfw3.h>

main.zig

const std = @import("std");
const c = @import("c");

pub fn main(init: std.process.Init) !void {
    _ = init;
    if (c.glfwInit() == 0) return error.GlfwInitFailed;
    errdefer c.glfwTerminate();

    return error.DeliberateTestError;
}

Adding or removing use_llvm or link_libc does not change anything.

I still get the same result:

$ zig build run
thread 12595 panic: attempt to use null value
error return context:

Once again, the code run fine when main do not return an error.

I have a windows machine, i will try to test this code on it…

No, I didn’t expect so, was just clarifying that I did make one modification to your example, but this is because of an unrelated linking issue.

My point being is that I don’t think your problem is related to your code or what you are doing in Zig, but something else in the environment. I apologize, I know this is not very helpful in narrowing it down, but it does show that your code runs normally outside of your environment.

1 Like

That actually helps, thank you! Knowing that the exact same code runs normally on your end confirms that my Zig code isn’t the problem here.

For now, I think I will switch over to using the package from GitHub - tiawl/glfw.zig: @glfw packaged for @ziglang · GitHub

If I manage to pin down exactly what is broken in my Gentoo/Wayland environment later on, I will make sure to update this thread with the solution.

I tested my code on Windows, and it worked correctly. Returning an error from main does not cause a panic there, so this confirms again that the issue is related to my environment rather than the code itself.

My Gentoo setup is probably too minimal and may be missing something required at runtime :sweat_smile:

I also tried using the packaged GLFW from tiawl, but I got the same result. It’s strange because I can render quads with OpenGL, handle the window, and process input correctly, yet returning an error still causes the issue…