# Am I using Io.failing wrong?

**URL:** <https://ziggit.dev/t/am-i-using-io-failing-wrong/18091>\
**Category:** Help\
**Tags:** standard-library, testing, io\
**Created:** [October 25, 2026, 7:14am UTC](https://ziggit.dev/t/am-i-using-io-failing-wrong/18091 "2026-10-25T07:14:25Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![truly-not-taken](https://ziggit.dev/user_avatar/ziggit.dev/truly-not-taken/32/6205_2.png) [@truly-not-taken](https://ziggit.dev/u/truly-not-taken)\
**Post date:** [October 25, 2026, 7:14am UTC](https://ziggit.dev/t/am-i-using-io-failing-wrong/18091/1 "2026-10-25T07:14:25Z")

</div>

This test spins in an infinite loop:

```zig
test "failing Io fails to flush in positional mode" {
    var buffer: [128]u8 = undefined;
    var stdout: std.Io.File.Writer = .init(.stdout(), .failing, &buffer);
    try stdout.interface.writeAll("hello");
    const actual = stdout.interface.flush(); // spins CPU forever
    try std.testing.expectError(error.WriteFailed, actual);
}

```

Any other `failing` seem to work as expected:

```zig
test "failing Allocator fails" {
    const actual = std.mem.Allocator.failing.dupe(u8, "hello");
    try std.testing.expectError(error.OutOfMemory, actual);
}

test "failing Writer fails" {
    var writer = std.Io.Writer.failing;
    const actual = writer.writeAll("hello");
    try std.testing.expectError(error.WriteFailed, actual);
}

test "failing Io fails if used in streaming mode" {
    var buffer: [128]u8 = undefined;
    var stdout: std.Io.File.Writer = .initStreaming(.stdout(), .failing, &buffer);
    try stdout.interface.writeAll("hello");
    const actual = stdout.interface.flush();
    try std.testing.expectError(error.WriteFailed, actual);
    try std.testing.expectEqual(error.InputOutput, stdout.err.?);
}

test "failing Io fails in positional mode if the writer is unbuffered" {
    var stdout: std.Io.File.Writer = .init(.stdout(), .failing, &.{});
    const actual = stdout.interface.writeAll("hello");
    try std.testing.expectError(error.WriteFailed, actual);
    try std.testing.expectEqual(error.BrokenPipe, stdout.err.?);
}

```

The offending loop seems go like this:

```zig
// std.Io.Writer
pub fn flush(w: *Writer) Error!void {
    return w.vtable.flush(w);
}

// std.Io.Writer
pub fn defaultFlush(w: *Writer) Error!void {
    const drainFn = w.vtable.drain;
    while (w.end != 0) _ = try drainFn(w, &.{""}, 1);
}

// std.Io.File.Writer
pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
    const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
    switch (w.mode) {
        .positional, .positional_simple => return drainPositional(w, data, splat),
    // ... i cut irrelevant lines
}

// std.Io.File.Writer
fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
    const io = w.io;
    const header = w.interface.buffered();
    const n = io.vtable.fileWritePositional(io.userdata, w.file, header, data, splat, w.pos) catch |err| switch (err) {
        // ... i cut here because n == 0
    };
    w.pos += n;
    return w.interface.consume(n); // consumes zero bytes
}

// std.Io
pub fn failingFileWritePositional(userdata: ?*anyopaque, file: File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize {
    _ = userdata;
    _ = file;
    _ = header;
    _ = offset;
    for (data[0 .. data.len - 1]) |item| {
        if (item.len > 0) return error.BrokenPipe;
    }
    if (data[data.len - 1].len != 0 and splat != 0) return error.BrokenPipe;
    return 0; // <<<<<<<======= why???
}

```

So it turns out `failing` Io reports `0` bytes successfully written when the data are only in the `header`. Why? Then `flush()` sees that the buffer is not empty and tries again.

Am I doing it backwards? Is it a bug in the implementation of `Io.failing`? Is it a bug in `Io` interface design that does not allow to return an error there? Is it a bug in `flush()` implementation? Is it a bug in `Io.Writer` interface design that forces `flush()` to loop until the buffer is empty?

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [October 25, 2026, 7:57am UTC](https://ziggit.dev/t/am-i-using-io-failing-wrong/18091/2 "2026-10-25T07:57:22Z")

</div>

> [@truly-not-taken](#):
>
> So it turns out `failing` Io reports `0` bytes successfully written when the data are only in the `header`. Why?

probably wanted to make writes of 0 data not fail for some reason and forgot to check the header.

---

<div class="post-metadata">

**Author:** ![truly-not-taken](https://ziggit.dev/user_avatar/ziggit.dev/truly-not-taken/32/6205_2.png) [@truly-not-taken](https://ziggit.dev/u/truly-not-taken)\
**Post date:** [October 25, 2026, 7:58am UTC](https://ziggit.dev/t/am-i-using-io-failing-wrong/18091/3 "2026-10-25T07:58:20Z")

</div>

Should I file a bug report?

---

<div class="post-metadata">

**Author:** ![truly-not-taken](https://ziggit.dev/user_avatar/ziggit.dev/truly-not-taken/32/6205_2.png) [@truly-not-taken](https://ziggit.dev/u/truly-not-taken)\
**Post date:** [October 25, 2026, 8:32am UTC](https://ziggit.dev/t/am-i-using-io-failing-wrong/18091/4 "2026-10-25T08:32:20Z")

</div>

[https://codeberg.org/ziglang/zig/issues/37406](https://codeberg.org/ziglang/zig/issues/37406)
