Ziggit - Latest topics https://ziggit.dev/latest Latest topics Wed, 18 Mar 2026 15:47:32 +0000 Wasm32-emscripten now requires Emscripten filesystem emulation (side effect of new IO?) Brainstorming I just noticed a new-ish emcc-link-problem when building the sokol-zig samples (GitHub - floooh/sokol-zig: Zig bindings for the sokol headers (https://github.com/floooh/sokol) · GitHub) for wasm32-emscripten (via zig build -Dtarget=wasm32-emscripten examples) using the latest Zig nightly.

This started to fail in the emcc linker step with a missing symbol required by the accept4 syscall:

error: undefined symbol: $SOCKFS (referenced by $getSocketFromFD, referenced by __syscall_accept4, referenced by root reference (e.g. compiled C/C++ code))
warning: To disable errors for undefined symbols use `-sERROR_ON_UNDEFINED_SYMBOLS=0`

TL;DR: this says that even programs that don’t call socket functions now depend on a socket syscall for accept().

This is a linker error because I’m linking with the emcc option -sNO_FILESYSTEM=1 which essentially disables the Emscripten POSIX IO emulation (e.g. the problem is easy to fix by just not passing this option to the linker - but this increases the ‘binary size’).

It made me think though: why does a Zig program that doesn’t even use socket functionality all of the sudden pull in the socket accept() function, when Zig has a ‘lazy’ compilation model which only builds reachable code?

Is this a side effect of the new IO being a virtual method interface which essentially kills dead-code-elimination?

If that’s the reason, will this problem (and IMHO it’s a pretty big problem) even be fixable? It would kinda suck if code that’s never going to be called is linked into each and every Zig program.

If this is not fixable in the compiler, are there plans for a ‘minimal/embedded IO’ which doesn’t require files and sockets? (or maybe a ‘modular IO’ that can be configured via build options - e.g. similar to how I can pass -sNO_FILESYSTEM=1 to Emscripten which promises that I will not call any code which depends on POSIX IO).

PS: resulting size comparison (uncompressed, in bytes):

Zig 0.15.2 with Emscripten -sNO_FILESYSTEM=1 and release=small:

clear.js: 23620 bytes
clear.wasm: 33443 bytes

Current Zig nightly with Emscripten filesystem emulation enabled, release=small:

clear.js: 58078 bytes
clear.wasm: 84700 bytes

Even though it’s “just” a couple dozen kbytes, for small programs this overhead is quite substantial…

1 post - 1 participant

Read full topic

]]>
https://ziggit.dev/t/wasm32-emscripten-now-requires-emscripten-filesystem-emulation-side-effect-of-new-io/14644 Wed, 18 Mar 2026 15:47:32 +0000 No No No ziggit.dev-topic-14644 Wasm32-emscripten now requires Emscripten filesystem emulation (side effect of new IO?)
Non-blocking database calls in immediate-mode UI (raylib/raygui std.Io vs std.Thread?) Explain I have an application using raylib/raygui this is drawing UI in immediate mode.
for this application I need to do a database call using input retrieved from the UI. This database call is a bit heavy and can take a sec to complete. If I would handle this on the main thread that means the UI would block until that request is done.

Here the application:

const std = @import("std");
const rl = @import("raylib");
const rg = @import("raygui");
const ui = @import("ui.zig");

const Context = @import("Context.zig");
const Io = std.Io;

pub fn main(init: std.process.Init) !void {
    const gpa = init.gpa;
    const io = init.io;

    var context: Context = try .init(io, gpa);
    defer context.deinit(gpa);

    try context.fetchData(gpa);

    rl.setConfigFlags(.{
        .window_resizable = true,
        .window_topmost = true,
    });
    rl.initWindow(1280, 720, "KPI Viewer");
    defer rl.closeWindow();

    rl.maximizeWindow();

    rl.setTargetFPS(60);

    rg.setStyle(.default, .{ .default = .text_size }, 20);

    var search_buf: [16:0]u8 = @splat(0);
    var select_props: ui.SelectProps = .{
        .rectangle = .{ .x = 30, .y = 30, .width = 200, .height = 24 },
        .value = null,
        .values = context.herds.items,
        .filtered = try gpa.alloc([*:0]const u8, context.herds.items.len),
        .search_text = &search_buf,
        .search_active = false,
        .dropdown_active = false,
    };
    select_props.setValue(std.mem.span(select_props.values[0]));
    defer gpa.free(select_props.filtered.ptr[0..context.herds.items.len]);

    while (!rl.windowShouldClose()) {
        rl.beginDrawing();
        defer rl.endDrawing();

        if (ui.select(&select_props)) {
            if (select_props.value) |id| {
                _ = id;
                // id is selected, now I need to do expensive database call without blocking UI
                // preferebly storing it in 'context'
            }
        }

        rl.clearBackground(.white);
    }
}

With the new Io I’m not sure how I could handle this in the best and most idiomatic way? Should the std.Io be used here, std.Thread or a combination of both?

  • With Io.async/concurrent I would need to await to ensure it’s completed, as far as I understand, this is a blocking call so I should not do that inside the main loop.
  • Maybe I could use Io.Group as I read in another post that that should be used for running things in a detached way? But reading the comments it mentions that it’s does not guarentee execution until await is called, so again the same problem.
  • Or post the request in a Io.Queue and have a seperate worker thread pick it up, this thread could be spawned using std.Thread or have a polling job on Io.Group? But now, I need some mechanism to indicate that the work is done and the results can be shown.

1 post - 1 participant

Read full topic

]]>
https://ziggit.dev/t/non-blocking-database-calls-in-immediate-mode-ui-raylib-raygui-std-io-vs-std-thread/14643 Wed, 18 Mar 2026 14:10:21 +0000 No No No ziggit.dev-topic-14643 Non-blocking database calls in immediate-mode UI (raylib/raygui std.Io vs std.Thread?)
A Sega Genesis/Mega Drive emulator mostly written in Zig Showcase Hi everyone,

I’ve made an early version of a Sega Genesis/Mega Drive emulator, which is mostly written in Zig with C implementations for some of the components. At the moment, the project is far from finished, but the emulator boots some games with good compatibility. In any case, if you’re interested, the project is available on GitHub (here: https://github.com/pixel-clover/sandopolis).

Feedback is welcome.

1 post - 1 participant

Read full topic

]]>
https://ziggit.dev/t/a-sega-genesis-mega-drive-emulator-mostly-written-in-zig/14642 Wed, 18 Mar 2026 13:00:41 +0000 No No No ziggit.dev-topic-14642 A Sega Genesis/Mega Drive emulator mostly written in Zig
Nonuniformity in documentation comments makes for ugly `zig std` Help For instance reading std.hash_map.HashMap the documentation comment in the actual code looks great:

/// General purpose hash table.
/// No order is guaranteed and any modification invalidates live iterators.
/// It provides fast operations (lookup, insertion, deletion) with quite high
/// load factors (up to 80% by default) for low memory usage.
/// For a hash map that can be initialized directly that does not store an Allocator
/// field, see `HashMapUnmanaged`.
/// If iterating over the table entries is a strong usecase and needs to be fast,
/// prefer the alternative `std.ArrayHashMap`.
/// Context must be a struct type with two member functions:
///   hash(self, K) u64
///   eql(self, K, K) bool
/// Adapted variants of many functions are provided.  These variants
/// take a pseudo key instead of a key.  Their context must have the functions:
///   hash(self, PseudoKey) u64
///   eql(self, PseudoKey, K) bool

But when looking at the website it doesn’t translate softline breaks to linebreaks resulting in ugly formatting. What should change, autodoc or the documentation comment?

std.Io bothers me every time too, last line… (here’s the text I can’t embed another image)

  • memory mapped files This interface allows programmers to write optimal, reusable code while participating in these operations.

3 posts - 3 participants

Read full topic

]]>
https://ziggit.dev/t/nonuniformity-in-documentation-comments-makes-for-ugly-zig-std/14640 Wed, 18 Mar 2026 11:04:38 +0000 No No No ziggit.dev-topic-14640 Nonuniformity in documentation comments makes for ugly `zig std`
Workaround to modify a const value without the protection from @constCast, yet the test expects initialization value Explain so, I was writing a store for dynamic value (just a wrapper for std.HashMap) and found out that you can modify a const value by passing its pointer then doing a asBytes()

the file here [should I paste it here]

// بسم الله الرحمن الرحيم
// la ilaha illa Allah Mohammed Rassoul Allah

map: std.StringArrayHashMap([]u8),

pub fn init(allocator: std.mem.Allocator) Self {
    return .{
        .map = .init(allocator),
    };
}

pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
    var iter = self.map.iterator();
    while (iter.next()) |entry| allocator.free(entry.value_ptr.*);
    self.map.deinit();
}

/// adds the value itself to the map after duping it
///
/// calling it like `try map.add("123", 123, allocator);`
/// whill store a heap allocated int
pub fn add(self: *Self, key: []const u8, value: anytype, allocator: std.mem.Allocator) !void {
    const slice: []const u8 = std.mem.asBytes(&value);
    const slice_allocated: []u8 = try allocator.dupe(u8, slice);
    errdefer allocator.free(slice_allocated);
    try self.map.put(key, slice_allocated);
}

pub fn get(self: *Self, key: []const u8) ?[]u8 {
    return self.map.get(key);
}

pub fn getAs(self: *Self, key: []const u8, T: type) ?*T {
    const raw: []u8 = self.map.get(key) orelse return null;
    const ptr: *T = @alignCast(std.mem.bytesAsValue(T, raw));
    return ptr;
}

test "multiple types" {
    const testing = std.testing;
    var store: Self = .init(testing.allocator);
    defer store.deinit(testing.allocator);

    var a: u32 = 31;
    try store.add("a", a, testing.allocator);
    try store.add("a_ptr", &a, testing.allocator);

    const a_entry: *u32 = store.getAs("a", u32) orelse unreachable;
    a_entry.* = 0;
    try testing.expect(a == 31);
    try testing.expect(a_entry.* == 0);

    const a_ptr_entry: **u32 = store.getAs("a_ptr", *u32) orelse unreachable;
    a_ptr_entry.*.* = 8;
    try testing.expect(a_entry.* == 0);
    try testing.expect(a_ptr_entry.* != a_entry);
    try testing.expectEqual(a_ptr_entry.*, &a);
    try testing.expectEqual(a, 8);
}

const Self = @This();
const std = @import("std");

I get error: 'store.test.multiple types' failed: expected 31, found 8 unless I change const a: u32 = 31; to var a: u32 = 31;

the question is:

why does try testing.expectEqual(a_ptr_entry.*, &a); pass and at the same time try testing.expectEqual(a, 8); fails? I assume that for the latter it checks for a at comptime because a is a const so any modifications to it might be a programmer bug.

final note

dis is a programmer’s fault probably, idk if this is allowed to be considered a bug. it’s just: idk.

3 posts - 2 participants

Read full topic

]]>
https://ziggit.dev/t/workaround-to-modify-a-const-value-without-the-protection-from-constcast-yet-the-test-expects-initialization-value/14637 Wed, 18 Mar 2026 08:40:35 +0000 No No No ziggit.dev-topic-14637 Workaround to modify a const value without the protection from @constCast, yet the test expects initialization value
Integrating Zig in a multi-language project? Help I’ve got a project at work with the following key development dependencies:

  1. linux environment (wsl (ubuntu) or linux (ubuntu))
  2. uv (for python)
  3. docker
  4. ansible (via uv)
  5. < 10 other python packages (ruff, ty, etc)

So I’ve got a shell script (bootstrap.sh) that installs uv and docker on developer machines, then I’ve got some top-level shell-ish script implemented in python called work.py.

Developers call various functions in the script:

  • uv run work.py deploy something is used by CI for deployment (ansible dependency provided by uv)
  • uv run work.py test runs on developer machines and CI (calls pytest)
  • uv run work.py format (just shells to ruff)

The target environments are low-tier linux with docker installed (< 8 GB ram, < 4 CPUs).

In this system, uv is my build system (caching my dependencies), but work.py is not a build system, its just something more readable to me than bash. Its only about 1000 lines and can deploy to 10 different environments, etc.

So right now its a single-language project, everything works and the development experience is quite pleasant. Onboarding new developers is just bootstrap.sh, and uv keeps everything else aligned (except docker version), but we have hit some performance bottlenecks that can really only be solved with a compiled language in some small key areas. My team’s highest proficiency is python, by far, and I’m considering integrating Zig for the truly performance critical subsystems.

Heavy on my mind is the following talk from @matklad https://www.youtube.com/watch?v=jVC4DP-8xLM

The talk tells me to stick to the tools I have:

  • uv and docker

but that conflicts with the performance limitations I have extensively fought with python profilers etc. After a while of fighting the profilers, I end of writing some python code that looks horible and is just hard to understand and maintain (batching updates to dictionaries, little caching tricks, reducing function call overhead etc…).

My question is if and how I should integrate Zig?
My first project would wholly replace an existing python microservice with zig, reducing it from a 1 GB docker image to maybe 20 MB and reducing its dependencies to only Zig and a well-maintained c library.

  • Use zig as my build system (shelling to system installed uv,docker)?
  • keep my existing fairly simple build script, add the zig python package, and write python modules in zig?
  • Don’t integrate zig at all, and just buy bigger machines (lots of room here to upgrade the targets)?

Completely off the table is using bazel. Its just too hard to understand and teach to others (even for me who is comfortable using it).

3 posts - 3 participants

Read full topic

]]>
https://ziggit.dev/t/integrating-zig-in-a-multi-language-project/14636 Wed, 18 Mar 2026 04:26:37 +0000 No No No ziggit.dev-topic-14636 Integrating Zig in a multi-language project?
Blog post: efficient organization of many small dynamic arrays Showcase Hi all!

This is my first blog post, would love some feedback:
A stab at orienting my design around data

I’m newer to the zig and systems programming communities, so I’d love to here constructive criticism of both the blog post and of the implementation for which the blog post was spawned.

Best,
Gavin

1 post - 1 participant

Read full topic

]]>
https://ziggit.dev/t/blog-post-efficient-organization-of-many-small-dynamic-arrays/14635 Wed, 18 Mar 2026 03:04:55 +0000 No No No ziggit.dev-topic-14635 Blog post: efficient organization of many small dynamic arrays
Segmentation fault when reading data from a socket using std.Io Help I’m trying to use the new std.Io networking code to accept network connections. I am new to Zig & C programming, but not programming in general. The program I am writing consistently crashes with a segmentation fault when attempting to read data from a reader. I have tried running the code on Windows 11 and Ubuntu 22.

const std = @import("std");
const Io = std.Io;
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {
    const io = init.io;
    const address = try Io.net.IpAddress.parseIp4("127.0.0.1", 8080);
    var server = try Io.net.IpAddress.listen(address, io, .{ .reuse_address = true });
    while (true) {
        var stream = try server.accept(io);
        var read_buffer: [1024]u8 = undefined;
        var rdr = stream.reader(io, &read_buffer).interface;
        _ = try rdr.peek(1); <-- Segmentation fault here
    }
}

I’m running zig-x86_64-linux-0.16.0-dev.2905+5d71e3051, but have experienced the same issue with other 0.16.0-dev “releases.” For reference, the stack trace is

Segmentation fault at address 0x728
/home/ryanj/code/zig/zig-x86_64-linux-0.16.0-dev.2905+5d71e3051/lib/std/Io/net.zig:1281:40: 0x11dc7f0 in readVec (std.zig)
const n = io.vtable.netRead(io.userdata, r.stream.socket.handle, dest) catch |err| {
^
/home/ryanj/code/zig/zig-x86_64-linux-0.16.0-dev.2905+5d71e3051/lib/std/Io/Reader.zig:1124:56: 0x1031460 in fillUnbuffered (std.zig)
while (r.end < r.seek + n) _ = try r.vtable.readVec(r, &bufs);
^
/home/ryanj/code/zig/zig-x86_64-linux-0.16.0-dev.2905+5d71e3051/lib/std/Io/Reader.zig:1110:26: 0x10311e8 in fill (std.zig)
return fillUnbuffered(r, n);
^
/home/ryanj/code/zig/zig-x86_64-linux-0.16.0-dev.2905+5d71e3051/lib/std/Io/Reader.zig:511:15: 0x10a7a8b in peek (std.zig)
try r.fill(n);
^
/home/ryanj/code/zig/project/src/main.zig:14:25: 0x11d60f2 in main (main.zig)
_ = try rdr.peek(1);
^
/home/ryanj/code/zig/zig-x86_64-linux-0.16.0-dev.2905+5d71e3051/lib/std/start.zig:716:30: 0x11d6bef in callMain (std.zig)
return wrapMain(root.main(.{
^
/home/ryanj/code/zig/zig-x86_64-linux-0.16.0-dev.2905+5d71e3051/lib/std/start.zig:190:5: 0x11d5d41 in _start (std.zig)
asm volatile (switch (native_arch) {

Any help or guidance would be greatly appreciated.

3 posts - 3 participants

Read full topic

]]>
https://ziggit.dev/t/segmentation-fault-when-reading-data-from-a-socket-using-std-io/14633 Wed, 18 Mar 2026 01:22:22 +0000 No No No ziggit.dev-topic-14633 Segmentation fault when reading data from a socket using std.Io
How to explain the behavior of modifying the value referenced by the result of @constCast? Explain const std = @import("std"); const n: u8 = 1; pub fn main() void { const p: *const u8 = &n; const p2: *u8 = @constCast(p); p2.* = 2; // not panic but a no-op std.debug.print( \\{} {} {} \\ , .{p2.*, p.*, n} // 1 1 1 ); }

Should it crash instead?

13 posts - 4 participants

Read full topic

]]>
https://ziggit.dev/t/how-to-explain-the-behavior-of-modifying-the-value-referenced-by-the-result-of-constcast/14630 Tue, 17 Mar 2026 22:24:02 +0000 No No No ziggit.dev-topic-14630 How to explain the behavior of modifying the value referenced by the result of @constCast?
Why doesn't RLS apply through inline functions? Explain This code is valid and compiles:

const Foo = struct {
    bar: u8,
    qux: u16,

    pub inline fn getBar(self: @This()) u8 {
        return self.bar;
    }
};
var foo: Foo = ...;
const bar = &blk: {
    break :blk foo.bar;
};
bar.* = 2;
assert(bar == &foo.bar);

I think this is because of RLS: the & forwards a result location to break :blk foo.bar, so a copy is avoided.
Now, I believe inline fn is semantically similar to blocks. So why doesn’t this work?

var foo: Foo = ...;
const bar = &foo.getBar();
bar.* = 2;

In my head, the two should be equivalent, so RLS should still apply.
I know there has been discussion about why you can’t return a pointer-to-local from an inline fn, but I believe that’s because inline fns are like blocks, and you can’t return a pointer-to-local from those either. But this question is about RLS.

8 posts - 6 participants

Read full topic

]]>
https://ziggit.dev/t/why-doesnt-rls-apply-through-inline-functions/14629 Tue, 17 Mar 2026 21:18:47 +0000 No No No ziggit.dev-topic-14629 Why doesn't RLS apply through inline functions?
Accepting net clients asynchronously with new std.Io Help Heya !

Any idea how I would implement in zig’s std.Io.net accepting clients asynchronously ?
I used to set force_nonblocking in std.net.Address.ListenOptions and handle it from there.

However, it was deleted as std.Io as the new way to do Io things. As such, how would you idiomatically, using the new interface, force non-blocking client acceptance ? I would prefer if the solution doesn’t include spawning a new thread only for accepting clients.

In more details, I am remaking a minecraft server in TCP and I would need to be able to do ticks during other Io things. I am already quite well versed in the recents merges in Io and its internal workings (such as operate etc.). As such, I’m able to control the std.Io interface that I’m using if needed.

Thanks in advance !

3 posts - 2 participants

Read full topic

]]>
https://ziggit.dev/t/accepting-net-clients-asynchronously-with-new-std-io/14628 Tue, 17 Mar 2026 17:01:19 +0000 No No No ziggit.dev-topic-14628 Accepting net clients asynchronously with new std.Io
Https://github.com/ktarasov/love Showcase
github.com

A simple joke utility that does only one thing — after launching, it outputs the phrase “I love you!” to the console. If you pass a parameter with a name, the phrase will include that name. For example: “I love you, Jane!”.

The utility automatically detects the user’s locale and, if there is a message for the desired locale, outputs it.

Inspired by this picture:

:rofl:

1 post - 1 participant

Read full topic

]]>
https://ziggit.dev/t/https-github-com-ktarasov-love/14627 Tue, 17 Mar 2026 14:38:48 +0000 No No No ziggit.dev-topic-14627 Https://github.com/ktarasov/love
How to store constant pointers in a buffer at compile time? Help I tested the following code, and they can all assign values to variables at compile time.


test "comptime" {
    const num: usize = 0;
    comptime {
        var ptr: *const anyopaque = undefined;
        ptr = &num;
    }
    comptime {
        var array: [10]*const anyopaque = undefined;
        for (&array) |*item| {
            item.* = &num;
        }
    }
    comptime {
        var buf: [64]u8 = undefined;
        const slice: []align(1) usize = std.mem.bytesAsSlice(usize, &buf);
        slice[0] = 100;
    }
    comptime {
        var buf: [64]u8 = undefined;
        const slice: []align(1) *const anyopaque = std.mem.bytesAsSlice(*const anyopaque, &buf);
        slice[0] = @ptrFromInt(100);
    }
}

But when I tried another piece of code, it resulted in an error.

test "comptime_error" {
    comptime {
        const num1: usize = 0;
        var buf: [64]u8 = undefined;
        const slice: []align(1) *const anyopaque = (std.mem.bytesAsSlice(*const anyopaque, &buf));
        slice[0] = &num1;
    }
}
 error: value stored in comptime field does not match the default value of the field
        slice[0] = &num1;
        ~~~~~~~~~^~~~~~~

Why does this happen? If the previous code snippets work, why does this one result in an error?

Can this issue be resolved?

4 posts - 3 participants

Read full topic

]]>
https://ziggit.dev/t/how-to-store-constant-pointers-in-a-buffer-at-compile-time/14626 Tue, 17 Mar 2026 14:12:37 +0000 No No No ziggit.dev-topic-14626 How to store constant pointers in a buffer at compile time?
Zroaring — roaring bitmaps for zig Showcase https://codeberg.org/archaistvolts/zroaring

a zig port of CRoaring

roaring bitmaps are a data structure for storing and operating on large sets of 32-bit integers. they’re used in major systems because of great compression and fast set operations.

Documentation

this project is quite early with lots to be done. its a little messy still but i think i’m ok with the current design except some regret that i’ve over engineered my WordBitset with excessive comptime options.

just wanted to share incase others are interested in this. contributions welcome! no ai slop please.

2 posts - 1 participant

Read full topic

]]>
https://ziggit.dev/t/zroaring-roaring-bitmaps-for-zig/14625 Tue, 17 Mar 2026 12:42:34 +0000 No No No ziggit.dev-topic-14625 Zroaring — roaring bitmaps for zig
-fsanitize-thread Help Context:

  • zig version: 0.15.2
  • OS: NixOS, Ubuntu

I’ve been trying to understand how to get the -fsanitize-thread option working and not having a great deal of success.

I have a small example program here that definitely has a race that helgrind happily confirms:

const std = @import(“std”);

var bank_account: i32 = 200;

pub fn main() !void {
    var creditor = try std.Thread.spawn(.{}, credit, .{});
    var debitor = try std.Thread.spawn(.{}, debit, .{});
    creditor.join();
    debitor.join();
    std.debug.print(“balance: ‘{d}’\n”, .{bank_account});
}

pub fn credit() void {
    var i: usize = 0;
    while (i < 1000) : (i += 1) {
        var balance = bank_account;
        balance += 20;
        bank_account = balance;
    }
}

pub fn debit() void {
    var i: usize = 0;
    while (i < 1000) : (i += 1) {
        var balance = bank_account;
        balance -= 20;
        bank_account = balance;
    }
}

When I build this code, I use:

zig build-exe -fsanitize-thread threads.zig

However, when I execute the resulting binary, I’m not seeing any output from TSAN, even when increasing the verbosity.

TSAN_OPTIONS=“verbosity=3” ./threads

Unfortunately, I haven’t been able to find a lot of information online regarding the usage or behavior of this build option. If anybody could offer some suggestions or pointers, I’d greatly appreciate it! :slight_smile:

3 posts - 2 participants

Read full topic

]]>
https://ziggit.dev/t/fsanitize-thread/14624 Tue, 17 Mar 2026 02:54:52 +0000 No No No ziggit.dev-topic-14624 -fsanitize-thread
How to Decide When to Free Memory in Complex Allocator Flows Help Hello Everyone,

I’m new to lower‑level programming languages, and Zig has been a great introduction. I’ve mostly worked with Python, where memory management is handled for you, so manually managing memory in Zig especially understanding when and how to free it has been challenging for me.

I’ve built a WIP GapBuffer implementation. The snippet below shows a simple insert operation that adds a single character. Internally, it may grow the buffer by allocating new memory and copying the existing data. Another helper, pushRightDataToEnd, rearranges the buffer by copying part of the data to the end of the current allocation

I’m unsure about the correct points to free allocated memory, which is causing both segfaults and leaks. I’ve added comments in the code to show all the places where I attempted to free memory.

Also, I have a pattern in insert method where I have function signature like (self: *GapBuffer, allocator: Allocator, data: []u8, char: u8), where I am passing the struct pointer, allocator, existing data and the character to be inserted. data argument is constant therefore, assign it to a new mutable variable new_data to let it mutate. After all the modifications we return the new_data back to the caller, is this a good pattern?

//! an example to display memory management issue (seg-fault for now)
//! Note, it is not the exact logic to implement gap buffer
//! this is truncated and modified to display of the said issue.
//! I am also not formatting the code to shorten the example by lines.
const std = @import("std");
const Allocator = std.mem.Allocator;
const print = std.debug.print;
/// GapBuffer struct
const GapBuffer = struct {
    gap_start: usize = 0,
    cursor_pos: usize = 0,
    gap_end: usize,
    capacity: usize,
    /// init method
    pub fn init(capacity: usize) !GapBuffer {
        return .{ .gap_end = capacity - 1, .capacity = capacity };
    }
    /// grow method when a buffer is full
    fn grow(self: *GapBuffer, allocator: Allocator, data: []u8) ![]u8 {
        // allolcate a new memory space
        const new_data = try allocator.alloc(u8, data.len + self.capacity);
        const right_data_start = self.gap_end + 1;
        const right_data_end = data.len - 1;
        var right_data: []u8 = &.{};
        if (right_data_start <= right_data_end) { right_data = data[right_data_start .. right_data_end + 1]; }
        // memcpy right data of the gap ends to the end of the new_data []u8.
        @memcpy(new_data[new_data.len - right_data.len ..], right_data);
        self.gap_end += self.capacity;
        // return new_data
        return new_data;
    }
    /// insert a character to the buffer
    pub fn insert(self: *GapBuffer, allocator: Allocator, data: []u8, char: u8) ![]u8 {
        // QUESTION: is it a good pattern?
        // data variable being a constant, use a new variable (new_data)
        // and finally return new data.

        // data being a constant, use a new variable (new_data)
        // so that we can mutate it later
        var new_data = data;
        // if the buffer is full then, free the buffer
        if (self.gap_end == self.gap_start) {
            // grow the buffer
            new_data = try self.grow(allocator, data);
            // QUESTION: free the buffer? may be not sure
            // allocator.free(new_data);
        }
        // if user wants to insert data at the cursor location
        if (self.cursor_pos != self.gap_start) {
            // push the right side data of the cursor to the end of the buffer
            new_data = try self.pushRightDataToEnd(allocator, new_data);
            // QUESTION: free the buffer? may be not sure
            // allocator.free(new_data);
        }
        // finally, assign the character at the gap start
        new_data[self.gap_start] = char;
        self.gap_start += 1;
        self.cursor_pos = self.gap_start;
        // return the new buffer data
        return new_data;
    }
    /// push the right side data of the cursor to the end of the buffer
    fn pushRightDataToEnd(self: *GapBuffer, allocator: Allocator, data: []u8) ![]u8 {
        const new_data = try allocator.alloc(u8, data.len);
        const left_data: []u8 = data[0..self.cursor_pos];
        @memcpy(new_data[0..self.cursor_pos], left_data);

        const right_data_start = self.cursor_pos;
        const right_data_end = self.gap_start - 1;
        var right_data: []u8 = &.{};
        if (right_data_start <= right_data_end) { right_data = data[right_data_start .. right_data_end + 1]; }
        // copy right of the gap
        const new_right_data_start = data.len - right_data.len;
        @memcpy(new_data[new_right_data_start..], right_data);
        self.gap_start = self.cursor_pos;
        self.gap_end = new_right_data_start - 1;
        return new_data;
    }
};

test "test left cursor movement" {
    const capacity: u8 = 3;
    var gap_buffer = try GapBuffer.init(capacity);
    const allocator = std.testing.allocator;
    const data = try allocator.alloc(u8, capacity);
    // QUESTION: May be free buffer here
    defer allocator.free(data);
    const insert_times: u8 = 3;
    // QUESTION: I also do not like the idea of using a new variable(new_data)
    // can we grow and shrink the original(data) buffer and insert the characters to the original buffer?
    // First set of inserts
    var new_data: []u8 = undefined;
    for (0..insert_times) |i| { new_data = try gap_buffer.insert(allocator, data, @intCast(97 + i)); }
    // QUESTION: May be free buffer here
    allocator.free(new_data);
    // second set of inserts
    for (0..insert_times) |i| { new_data = try gap_buffer.insert(allocator, new_data, @intCast(97 + i)); }
    // QUESTION: May be free buffer here
    allocator.free(new_data);
}

Any help will be really appreciated.

Cheers.

8 posts - 2 participants

Read full topic

]]>
https://ziggit.dev/t/how-to-decide-when-to-free-memory-in-complex-allocator-flows/14622 Mon, 16 Mar 2026 20:58:10 +0000 No No No ziggit.dev-topic-14622 How to Decide When to Free Memory in Complex Allocator Flows
Lo.zig - a lodash-style Zig library Showcase Just shipped the first release of lo.zig - a utility library built around Zig’s idioms

Two main design decisions:

  1. Zero hidden allocations. Functions that produce new data take an Allocator explicitly. Everything else operates on slices and returns sub-slices or scalars - no surprises

  2. Lazy iterators. map, filter, reject, without all return iterators. You can chain or call .collect(allocator) when you need a concrete slice

It covers slices (drop, take, first, last, zip, unzip, flatten, chunk, compact…), transforms (map, filter, reduce, forEach…), set operations (difference, intersection, union), search (find, indexOf, contains, every, some, none), map helpers (keysAlloc, valuesAlloc, entriesAlloc), strings (trimStart, trimEnd, startsWith, includes, replaceAll…), math (sum, mean, median, variance, stddev, lerp, remap), and type helpers (isNull, unwrapOr, coalesce, empty).

var it = lo.map(i32, i64, &.{ 1, 2, 3 }, double);
it.next(); // 2

lo.sum(i32, &.{ 1, 2, 3, 4 }); // 10
lo.variance(i32, &.{ 2, 4, 4, 4, 5, 5, 7, 9 }); // 4.0
lo.indexOf(i32, &.{ 10, 20, 30 }, 20); // 1

Install
zig fetch --save https://github.com/OrlovEvgeny/lo.zig/archive/refs/tags/v1.0.1.tar.gz

Feedback and PRs are welcome - I’m curious what functions people would want to see next

https://github.com/OrlovEvgeny/lo.zig

1 post - 1 participant

Read full topic

]]>
https://ziggit.dev/t/lo-zig-a-lodash-style-zig-library/14620 Mon, 16 Mar 2026 10:28:55 +0000 No No No ziggit.dev-topic-14620 Lo.zig - a lodash-style Zig library
Question regarding Dir.openFile in Threaded IO Help After checking Threaded.zig I found that all the openDirFile* functions return File with .flags = .{ .nonblocking = false }, does it mean that every file we open will block on read? I am not sure if we suppose to set this flag manually, like the batchAwaitAsync function of the Io interface when implemented as Threaded expects nonblocking to be true for concurrent execution: batchDrainSubmittedWindows

6 posts - 3 participants

Read full topic

]]>
https://ziggit.dev/t/question-regarding-dir-openfile-in-threaded-io/14619 Mon, 16 Mar 2026 09:49:58 +0000 No No No ziggit.dev-topic-14619 Question regarding Dir.openFile in Threaded IO
Differences between ZON and Zig Brainstorming There are differences between ZON and Zig:

  1. ZON has nan, inf, -inf literals, zig does not.

Side note: are -0.0 and +0.0 literals needed?

Should these differences be eliminated (add these literals to zig)?

Click to view the poll.

5 posts - 3 participants

Read full topic

]]>
https://ziggit.dev/t/differences-between-zon-and-zig/14618 Mon, 16 Mar 2026 06:41:50 +0000 No No No ziggit.dev-topic-14618 Differences between ZON and Zig
Heap allocated struct faster than comptime calculated arrays Explain I did a small experiment:
My chessprogram computes all attackdata @comptime into hard constant arrays, so these are baked into the exe.
Now I did a clone which creates these into a heap-created struct with attackdata @runtime.
To my surprise the heap-version is faster.
Why could that be?

Bench:

Total nodes: 18999768562 13.772s 1379.5293 Mnodes/s (1379529270) // comptimes
Total nodes: 18999768562 13.089s 1451.5786 Mnodes/s (1451578573) // heap
const Data = struct {
    file_magics: [64]MagicEntry,
    main_magics: [64]MagicEntry,
    anti_magics: [64]MagicEntry,
    pawn_attacks_white: [64]u64,
    pawn_attacks_black: [64]u64,
    knight_attacks: [64]u64,
    king_attacks: [64]u64,
    rank_attacks: [64 * 64]u64,
    file_attacks: [64 * 64]u64,
    diag_main_attacks: [64 * 64]u64,
    diag_anti_attacks: [64 * 64]u64,
};

const MagicEntry = struct {
    mask: u64,
    magic: u64,
};

14 posts - 9 participants

Read full topic

]]>
https://ziggit.dev/t/heap-allocated-struct-faster-than-comptime-calculated-arrays/14615 Sun, 15 Mar 2026 23:24:15 +0000 No No No ziggit.dev-topic-14615 Heap allocated struct faster than comptime calculated arrays
Custom type indexing Help Hi, sorry if the answer to this is obvious, but I didn’t find anything on it: Does Zig provide a way to make indexing syntax like array[idx] usable for custom types?

3 posts - 2 participants

Read full topic

]]>
https://ziggit.dev/t/custom-type-indexing/14614 Sun, 15 Mar 2026 21:08:39 +0000 No No No ziggit.dev-topic-14614 Custom type indexing
Snake case Brainstorming Since I abondoned my job languages Delphi and C# and using Rust for a while I have become a fan of snake case.

I wish I could scientifically prove that

doSomethingComplicated

is more difficult on the eyes and more difficult to process for the brain than

do_something_complicated

18 posts - 13 participants

Read full topic

]]>
https://ziggit.dev/t/snake-case/14609 Sun, 15 Mar 2026 09:34:15 +0000 No No No ziggit.dev-topic-14609 Snake case
Zig fetch updating file modification times in extracted archives Help Background

Running zig fetch to download and extract an archive seems to update the modification time of files in the extracted package. This interferes with automake/autoreconf, similarly to how this article describes it: Timestamps, GNU autotools, and repositories

For context, I generally build my most used tools from source, and am attempting to leverage zig build as a “better Makefile” i.e. fetch upstream sources declared by build.zig.zon, run arbitrary system commands on downloaded packages, install artifacts to a common location.

Reproduction

Version

0.15.2

Fetching the upstream source

zig fetch --save=make https://ftp.gnu.org.uk/gnu/make/make-4.4.tar.gz

build.zig

(Please ignore the lack of .addFileInput() etc. which would semi-integrate this with caching - there’s obviously lots of stuff missing here)

const std = @import("std");

pub fn build(b: *std.Build) void {
    const install_dir = b.getInstallPath(.prefix, "make");
    std.log.info("Install Directory: {s}", .{install_dir});
    const make = b.dependency("make", .{});
    const make_root = make.path("");

    const configure = b.addSystemCommand(&.{
        "./configure",
        "--prefix",
        install_dir,
        "--disable-dependency-tracking",
    });
    configure.setCwd(make_root);

    const bootstrap = b.addSystemCommand(&.{ "sh", "build.sh" });
    bootstrap.setCwd(make_root);
    bootstrap.step.dependOn(&configure.step);

    const install = b.addSystemCommand(&.{"./make"});
    install.setCwd(make_root);
    install.step.dependOn(&bootstrap.step);
    b.getInstallStep().dependOn(&install.step);
}

This gives us the following when run:

WARNING: 'aclocal-1.16' is missing on your system.
         You should only need it if you modified 'acinclude.m4' or
         'configure.ac' or m4 files included by 'configure.ac'.
         The 'aclocal' program is part of the GNU Automake package:
         <https://www.gnu.org/software/automake>
         It also requires GNU Autoconf, GNU m4 and Perl in order to run:
         <https://www.gnu.org/software/autoconf>
         <https://www.gnu.org/software/m4/>
         <https://www.perl.org/>
make: *** [Makefile:651: aclocal.m4] Error 127

Now obviously, I could just go and install autotools - but the core problem is that this occurs as the modification time of the files has been changed, which causes the whole autoreconf-fest to launch into action.

If we download and manually extract the archive with tar, each file has its modification time given at the time of archive creation, which avoids the need for any autotools reconfiguration:

wget https://ftp.gnu.org.uk/gnu/make/make-4.4.tar.gz
tar xvf make-4.4.tar.gz
ls -la make-4.4
total 1264
drwxr-xr-x 10 kai kai   4096 Oct 31  2022 .
drwxrwxr-x  3 kai kai   4096 Mar 14 18:43 ..
-rw-r--r--  1 kai kai  93787 Oct 31  2022 ABOUT-NLS
-rw-r--r--  1 kai kai  55130 Oct 31  2022 aclocal.m4
-rw-r--r--  1 kai kai   4327 Oct 23  2022 AUTHORS
-rw-r--r--  1 kai kai   5225 Oct 31  2022 Basic.mk
drwxr-xr-x  2 kai kai   4096 Oct 31  2022 build-aux
-rw-r--r--  1 kai kai   1217 Oct 23  2022 build.cfg.in
-rw-r--r--  1 kai kai   5582 Oct 23  2022 builddos.bat
-rwxr-xr-x  1 kai kai   5398 Oct 29  2022 build.sh
-rw-r--r--  1 kai kai  12441 Oct 23  2022 build_w32.bat
-rw-r--r--  1 kai kai 242663 Oct 31  2022 ChangeLog
-rwxr-xr-x  1 kai kai 466869 Oct 31  2022 configure
-rw-r--r--  1 kai kai  19269 Oct 31  2022 configure.ac
-rw-r--r--  1 kai kai  35151 Dec 19  2021 COPYING
drwxr-xr-x  2 kai kai   4096 Oct 31  2022 doc
-rw-r--r--  1 kai kai  15766 Jun  1  2022 INSTALL
drwxr-xr-x  2 kai kai   4096 Oct 31  2022 lib
drwxr-xr-x  2 kai kai   4096 Oct 31  2022 m4
-rw-r--r--  1 kai kai   7622 Oct 25  2022 Makefile.am
-rw-r--r--  1 kai kai   5330 Oct 23  2022 makefile.com
-rw-r--r--  1 kai kai  60444 Oct 31  2022 Makefile.in
drwxr-xr-x  2 kai kai   4096 Oct 31  2022 mk
-rw-r--r--  1 kai kai  83578 Oct 31  2022 NEWS
drwxr-xr-x  2 kai kai   4096 Oct 31  2022 po
-rw-r--r--  1 kai kai   8654 Oct 31  2022 README
-rw-r--r--  1 kai kai   2854 Oct 23  2022 README.Amiga
-rw-r--r--  1 kai kai   4496 Oct 23  2022 README.customs
-rw-r--r--  1 kai kai  13738 Oct 23  2022 README.DOS
-rw-r--r--  1 kai kai   6654 Oct 23  2022 README.OS2
-rw-r--r--  1 kai kai  21219 Oct 23  2022 README.VMS
-rw-r--r--  1 kai kai  15544 Oct 23  2022 README.W32
-rw-r--r--  1 kai kai    200 Feb 10  2022 SCOPTIONS
drwxr-xr-x  3 kai kai   4096 Oct 31  2022 src
drwxr-xr-x  3 kai kai   4096 Oct 31  2022 tests
-rw-r--r--  1 kai kai    937 Feb 10  2022 vms_export_symbol_test.com

If we look into the global zig cache for the package (after running zig fetch …), each file has its modification time as the time of extraction:

total 1264
drwxr-xr-x 10 kai kai   4096 Mar 14 18:46 .
drwxr-xr-x  3 kai kai   4096 Mar 14 18:46 ..
-rw-rw-r--  1 kai kai  93787 Mar 14 18:46 ABOUT-NLS
-rw-rw-r--  1 kai kai  55130 Mar 14 18:46 aclocal.m4
-rw-rw-r--  1 kai kai   4327 Mar 14 18:46 AUTHORS
-rw-rw-r--  1 kai kai   5225 Mar 14 18:46 Basic.mk
drwxr-xr-x  2 kai kai   4096 Mar 14 18:46 build-aux
-rw-rw-r--  1 kai kai   1217 Mar 14 18:46 build.cfg.in
-rw-rw-r--  1 kai kai   5582 Mar 14 18:46 builddos.bat
-rwxrwxrwx  1 kai kai   5398 Mar 14 18:46 build.sh
-rw-rw-r--  1 kai kai  12441 Mar 14 18:46 build_w32.bat
-rw-rw-r--  1 kai kai 242663 Mar 14 18:46 ChangeLog
-rwxrwxrwx  1 kai kai 466869 Mar 14 18:46 configure
-rw-rw-r--  1 kai kai  19269 Mar 14 18:46 configure.ac
-rw-rw-r--  1 kai kai  35151 Mar 14 18:46 COPYING
drwxr-xr-x  2 kai kai   4096 Mar 14 18:46 doc
-rw-rw-r--  1 kai kai  15766 Mar 14 18:46 INSTALL
drwxr-xr-x  2 kai kai   4096 Mar 14 18:46 lib
drwxr-xr-x  2 kai kai   4096 Mar 14 18:46 m4
-rw-rw-r--  1 kai kai   7622 Mar 14 18:46 Makefile.am
-rw-rw-r--  1 kai kai   5330 Mar 14 18:46 makefile.com
-rw-rw-r--  1 kai kai  60444 Mar 14 18:46 Makefile.in
drwxr-xr-x  2 kai kai   4096 Mar 14 18:46 mk
-rw-rw-r--  1 kai kai  83578 Mar 14 18:46 NEWS
drwxr-xr-x  2 kai kai   4096 Mar 14 18:46 po
-rw-rw-r--  1 kai kai   8654 Mar 14 18:46 README
-rw-rw-r--  1 kai kai   2854 Mar 14 18:46 README.Amiga
-rw-rw-r--  1 kai kai   4496 Mar 14 18:46 README.customs
-rw-rw-r--  1 kai kai  13738 Mar 14 18:46 README.DOS
-rw-rw-r--  1 kai kai   6654 Mar 14 18:46 README.OS2
-rw-rw-r--  1 kai kai  21219 Mar 14 18:46 README.VMS
-rw-rw-r--  1 kai kai  15544 Mar 14 18:46 README.W32
-rw-rw-r--  1 kai kai    200 Mar 14 18:46 SCOPTIONS
drwxr-xr-x  3 kai kai   4096 Mar 14 18:46 src
drwxr-xr-x  3 kai kai   4096 Mar 14 18:46 tests
-rw-rw-r--  1 kai kai    937 Mar 14 18:46 vms_export_symbol_test.com

Workarounds

There’s a couple obvious workarounds:

  1. Manual download + extract with std.http.Client and the inbuilt standard library compression modules
  2. Zig-ify the upstream myself (a-la allyourcodebase) - I’m more using the Zig build system as a task runner instead of a full-fledged compiler, so this feels somewhat orthogonal to what I’m trying to accomplish

Questions

  1. Is what I’m doing just an abuse of the build system and its dependency management?
  2. If not, is this a bug/missed feature in how Zig handles dependency extraction?

2 posts - 2 participants

Read full topic

]]>
https://ziggit.dev/t/zig-fetch-updating-file-modification-times-in-extracted-archives/14606 Sat, 14 Mar 2026 19:07:06 +0000 No No No ziggit.dev-topic-14606 Zig fetch updating file modification times in extracted archives
Reorder zig zen points Brainstorming Shouldn’t Together we serve the users. be put at the top since it is the most import point?

P.S I read a blog sometime ago by a user here about how he was in a class to get a medic license (maybe first responder, sorry for my english) and when everyone were asked why are they here no one said: because I want to help save a life. The import message was to be clear about intent and say it clearly. Does anyone have a link to the article?

3 posts - 2 participants

Read full topic

]]>
https://ziggit.dev/t/reorder-zig-zen-points/14605 Sat, 14 Mar 2026 15:39:19 +0000 No No No ziggit.dev-topic-14605 Reorder zig zen points
ArrayList.resize() Showcase The standard library funcrion for array list, resize leaves added elements uninitialized. So I came up with this monstrosity

behold !

pub fn array_list_resize(
	list: anytype,
	allocator: if (is_managed(@TypeOf(list))) void else std.mem.Allocator, // pass in if unamanged
	count: usize,
	value: @typeInfo(@TypeOf(list.items)).pointer.child,
) std.mem.Allocator.Error!void {
	// what ArrayList.resize() should've been
	if (is_managed(@TypeOf(list)))
		try list.ensureTotalCapacity(count)
	else
		try list.ensureTotalCapacity(allocator, count);

	@memset(list.unusedCapacitySlice(), value);
	list.items.len = count;
}

inline fn is_managed(T: type) bool {
	const L = switch (@typeInfo(T)) {
		.pointer => |p| p.child,
		else => unreachable,
	};
	return @hasField(L, "allocator");
}

10 posts - 4 participants

Read full topic

]]>
https://ziggit.dev/t/arraylist-resize/14604 Sat, 14 Mar 2026 13:33:34 +0000 No No No ziggit.dev-topic-14604 ArrayList.resize()
Why does posix.recv require libc on Windows? Help zig version 0.15.2. Build the following code with zig build-exe -target x86_64-windows-gnu main.zig fails. Error message is dependency on libc must be explicitly specified in the build command.

const std = @import("std");
const posix = std.posix;
pub fn main() !void {
	const fd = try posix.socket(posix.AF.INET, posix.SOCK.STREAM, 0);
	defer posix.close(fd);
	_ = try posix.recv(fd, &[_]u8{}, 0);
}

Adding -lc after zig build-exe fixes the compilation error. Removing the posix.recv line without adding -lc also fixes the error.

I’m quite confused. I assume the socket family functions are from ws2_32.dll. Is ws2_32 part of libc on Windows? Why does posix.recv require libc while posix.socket doesn’t? Thanks for any help!

6 posts - 3 participants

Read full topic

]]>
https://ziggit.dev/t/why-does-posix-recv-require-libc-on-windows/14603 Sat, 14 Mar 2026 11:27:51 +0000 No No No ziggit.dev-topic-14603 Why does posix.recv require libc on Windows?
Type resolution for std.builtin.Type.StructField Help Is this supposed to work? I know there were a lot of changes to type resolution recently, so I figured I would try this again. It didn’t work before and it still doesn’t work. Its very similar to std.builtin.Type.StructField. Should it work?

pub const Argument = struct {
    name: [:0]const u8,
    type: type,
    default_value: @TypeOf(@This().type),
};

test {
    const my_arg: Argument = .{
        .name = "config-file",
        .type = []const u8,
        .default_value = "",
    };
    _ = my_arg;
}

zig test src/cli/test.zig 
src/cli/test.zig:4:35: error: struct 'test.Argument' has no member named 'type'
    default_value: @TypeOf(@This().type),
                           ~~~~~~~^~~~~
src/cli/test.zig:1:22: note: struct declared here
pub const Argument = struct {
                     ^~~~~~
referenced by:
    test_0: src/cli/test.zig:8:5

11 posts - 6 participants

Read full topic

]]>
https://ziggit.dev/t/type-resolution-for-std-builtin-type-structfield/14602 Sat, 14 Mar 2026 07:06:22 +0000 No No No ziggit.dev-topic-14602 Type resolution for std.builtin.Type.StructField
Zig stability during pre-1.0 churn Explain I’ve been coding in Zig for 6 months now, starting with a 20k LOC port from Rust, and I have yet to encounter a single problem. How is this possible in a language and std library under such heavy development? I have not used the new IO APIs yet, but still!

10 posts - 8 participants

Read full topic

]]>
https://ziggit.dev/t/zig-stability-during-pre-1-0-churn/14601 Sat, 14 Mar 2026 02:04:11 +0000 No No No ziggit.dev-topic-14601 Zig stability during pre-1.0 churn
How to create two mutually referencing pointers at compile time? Help At runtime, we can create two mutually referencing pointers like this:


test "runtime" {
    var x: *const anyopaque = undefined;
    var y: *const anyopaque = undefined;

    x = @ptrCast(&y);
    y = @ptrCast(&x);

    try std.testing.expectEqual(@intFromPtr(x), @intFromPtr(&y));
    try std.testing.expectEqual(@intFromPtr(y), @intFromPtr(&x));
}

However, when I attempt to create them at compile time, the following error occurs:


test "comptime" {
    const x, const y = comptime blk: {
        var x: *const anyopaque = undefined;
        var y: *const anyopaque = undefined;
        x = @ptrCast(&y);
        y = @ptrCast(&x);
        break :blk .{ x, y };
    };
    try std.testing.expectEqual(@intFromPtr(x), @intFromPtr(&y));
    try std.testing.expectEqual(@intFromPtr(y), @intFromPtr(&x));
}
error: runtime value contains reference to comptime var
    try std.testing.expectEqual(@intFromPtr(x), @intFromPtr(&y));

How can this be achieved?

TIPS: The code above is minimized for testing purposes; my goal is conceptually equivalent to creating a doubly linked list at compile time.

6 posts - 5 participants

Read full topic

]]>
https://ziggit.dev/t/how-to-create-two-mutually-referencing-pointers-at-compile-time/14599 Fri, 13 Mar 2026 10:42:19 +0000 No No No ziggit.dev-topic-14599 How to create two mutually referencing pointers at compile time?
Bare metal zig code on raspberry pico 2 Help Hi,

I’m having some trouble getting a raspberry pico 2 to run bare metal zig code (code available here ) and I’m hoping to get some help from the community.

I’m trying to do the basic embedded hello world to blink the on board LED, but i can’t get my code to run. The pico just reboots in usb flash mode immediately after i flash the uf2 file to it.

This is what i get when generating the uf2 file with picotool:

read_ph ph offset 52 #entries 4
read_sh sh offset 65860 #entries 7
Detected FLASH binary
Mapped segment 10000000->10000028 (10000000->10000028)
Mapped segment 10000028->10000060 (10000028->10000060)
Page 0 / 1 10000000

And here is the structure of the ELF file:

zig-out/bin/blink:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .vector_table 00000008  10000000  10000000  00010000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .ARM.exidx    00000020  10000008  10000008  00010008  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .text         00000038  10000028  10000028  00010028  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  3 .ARM.attributes 00000034  00000000  00000000  00010060  2**0
                  CONTENTS, READONLY
  4 .comment      0000006d  00000000  00000000  00010094  2**0
                  CONTENTS, READONLY

I have tried looking at example projects available online, but most of it is in C or python using complex full featured SDKs that make it hard to understand at a basic level what i need to do at the linking and building stages for my code to run.

I tried following the microzig getting started guide from scratch in a new project, but i get a similar result as with my own code: the compilation and uf2 generation complete without errors but the code doesn’t do anything and the chip reboots right away.

I don’t think anything is wrong with the chip itself because the pre-compiled uf2 blink example from the official docs runs as expected.

I guess the problem is related to the initial stack pointer and/or reset handler either not being at the right memory addresses or not pointing to the right addresses, but i’m not sure how to set that up properly.

If someone has managed to run some basic zig code on the pico 2 i would very much appreciate some help to understand how to set up the binary correctly so the chip can run it.

6 posts - 4 participants

Read full topic

]]>
https://ziggit.dev/t/bare-metal-zig-code-on-raspberry-pico-2/14597 Thu, 12 Mar 2026 22:08:55 +0000 No No No ziggit.dev-topic-14597 Bare metal zig code on raspberry pico 2