Segmentation fault after flushing to stdout

I’m trying to use the Writer.print function, but upon flushing I get this segfault:

Segmentation fault at address 0x79
/nix/store/d0kkd93aji32klh4npp5fzq82lb84p85-zig-0.16.0/lib/zig/std/Io.zig:453:29: 0x1039fb3 in operate (std.zig)
return io.vtable.operate(io.userdata, operation);
^
/nix/store/d0kkd93aji32klh4npp5fzq82lb84p85-zig-0.16.0/lib/zig/std/Io/File.zig:614:27: 0x11df519 in writeStreaming (std.zig)
return (try io.operate(.{ .file_write_streaming = .{
^
/nix/store/d0kkd93aji32klh4npp5fzq82lb84p85-zig-0.16.0/lib/zig/std/Io/File/Writer.zig:126:36: 0x11defb6 in drainStreaming (std.zig)
const n = w.file.writeStreaming(io, header, data, splat) catch |err| {
^
/nix/store/d0kkd93aji32klh4npp5fzq82lb84p85-zig-0.16.0/lib/zig/std/Io/File/Writer.zig:93:63: 0x11de9cc in drain (std.zig)
.streaming, .streaming_simple => return drainStreaming(w, data, splat),
^
/nix/store/d0kkd93aji32klh4npp5fzq82lb84p85-zig-0.16.0/lib/zig/std/Io/Writer.zig:319:39: 0x1036a32 in defaultFlush (std.zig)
while (w.end != 0) _ = try drainFn(w, &.{“”}, 1);
^
/nix/store/d0kkd93aji32klh4npp5fzq82lb84p85-zig-0.16.0/lib/zig/std/Io/Writer.zig:313:26: 0x112bb93 in flush (std.zig)
return w.vtable.flush(w);
^
/home/oskar/Dev/raytracing-zig/src/main.zig:10:21: 0x11d7434 in main (main.zig)
try writer.flush();
^
/nix/store/d0kkd93aji32klh4npp5fzq82lb84p85-zig-0.16.0/lib/zig/std/start.zig:737:30: 0x11d7eee in callMain (std.zig)
return wrapMain(root.main(.{
^
/nix/store/d0kkd93aji32klh4npp5fzq82lb84p85-zig-0.16.0/lib/zig/std/start.zig:190:5: 0x11d7201 in _start (std.zig)
asm volatile (switch (native_arch) {
^
run
└─ run exe raytracing_zig failure
error: process terminated with signal ABRT
failed command: /home/oskar/Dev/raytracing-zig/zig-out/bin/raytracing_zig

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

My program looks like this:

const std = @import(“std”);

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

var buf: [50]u8 = undefined;
const stdout = std.Io.File.stdout().writer(io, &buf);
var writer = stdout.interface;
try writer.print("hi {}", .{2});
try writer.flush();
}

Previously I used writeStreamingAll, which worked fine, but I needed a function that also formatted.
I’d be really grateful for any help regarding this error

Not sure if this fixes your Error but it should be const writer = &stdout.interface;
Otherwise your are copying the interface i think.

6 Likes

const actually

3 Likes

I got bitten by the same mistake.

I have two questions:

  1. Can someone please explain why using a copy of stdout.interface makes a difference to using a reference to stdout.interface? Even if it is a copy, shouldn’t it be a shallow copy, and any copied internal references should still point to the original valid memory locations?

  2. This is what I would consider a footgun: the code compiles and maybe appears to work (doesn’t crash when omitting writer.flush), but then crashes. It also violates what I recall (or maybe just imagine) being one of the Zig philosophies: the simplest way should be the correct way (using stdout.interface is simpler than &stdout.interface). Is there a way to write this code such that the incorrect way is a compile error?

1 Like

Maybe I also have to revisit my understanding of the fundamentals of the Zig language…

This code works fine:

var stdout = std.Io.File.stdout().writer(io, &buf);
try stdout.interface.print("hi {}\n", .{2});
try stdout.interface.flush();

and to my understanding so far, this should be exactly equivalent to factoring out the expression stdout.interface (and giving it the name writer):

var stdout = std.Io.File.stdout().writer(io, &buf);
const writer = stdout.interface;
try writer.print("hi {}\n", .{2});
try writer.flush();

but this doesn’t compile:

seg.zig:7:9: error: local variable is never mutated
    var stdout = std.Io.File.stdout().writer(io, &buf);
        ^~~~~~
seg.zig:7:9: note: consider using 'const'
seg.zig:9:15: error: expected type '*Io.Writer', found '*const Io.Writer'
    try writer.print("hi {}\n", .{2});
        ~~~~~~^~~~~~
seg.zig:9:15: note: cast discards const qualifier

So maybe the compiler does try to nudge me in the correct direction, but instead of shuffling around const and var until it compiles, I didn’t see that I needed to add &.

The data being copied is this

vtable: *const VTable,
buffer: []u8,
end: usize = 0,

Notice how there is not a pointer to implementation state.
Instead, implementations use @fieldParentPtr to convert a *Reader/*Writer, which is assumed to be a pointer to a field (you specify by name) in the implementation type.

By making, and using, a copy of the Reader/Writer field, you are breaking that assumption. But that assumption is currently can’t be verified, so the code just continues assuming it is correct, and treating the memory around the copy as though it is the implementation type when it is not, this is illegal behaviour, anything could happen e.g. operating on a different file you opened in an entirely different part of your program!!!.

I say “currently” specifically because there are plans to make it checked

Sure, but they require language changes in one of the following directions

  • a generic/trait/etc system that makes this a non issue
  • pinned types, have issues with zigs existing type system and memory model
  • pinned places, solves issues of pinned types, but has a pretty damning issue that I can’t remember atm.
  • borrow checker/lifetimes, drastically increases language and compiler complexity, slows compile times, false positives restrict possible code, false negatives defeat the point unless it is good enough.
  • other things I can’t think of.

I am infavour of changing the language to make this a compile error, but I dont have a solution to actually do that without contradicting zigs ideals.

The compile error is specifically about incorrect const because the Reader/Writer data needs to be mutated, whether it’s a copy or not; you need a var regardless, and with it, you get the unchecked illegal behaviour I described earlier.

6 Likes