Segmentation fault when reading data from a socket using std.Io

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.

Your problem is here. You are creating a copy of the interface, not getting a pointer to the stream reader’s interface variable. The details have been discussed in many other threads (namely Zig 0.15.1 reader/writer: Don't make copies of @fieldParentPtr()-based interfaces). You can read that if you want more detailed discussion.

In short, your fix is to capture the Stream.Reader in a variable and get the interface by pointer.

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 stream_reader = stream.reader(io, &read_buffer);
        var rdr = &stream_reader.interface;
        _ = try rdr.peek(1);
        // The following also works
        _ = try stream_reader.interface.peek(1);
    }
}
4 Likes

If you forget/type that doesn’t take the address &, var rdr being var will still compile and have the same problem again.

rdr can, and should be const and anything storing a pointer to the interface should be const unless you do need to change the interface being used.
zig doesn’t complain about it not being mutated because its falsely detects rdr.peek as a mutation.

I go a step further and avoid putting the interface pointer in a variable, though there are certainly cases where that is less ergonomic.