What am I doing wrong here?

This is supposed to be super simple code. Just open a file and write a line of text to it through the new writer interface. Yet I can’t get it to work.

const std = @import("std");

pub fn main() !void {
    var output_file = try std.fs.createFileAbsolute("/home/cleong/Desktop/hello.txt", .{});
    defer output_file.close();
    var buffer: [4096]u8 = undefined;
    var interface = output_file.writer(&buffer).interface;
    try interface.print("This is a test\n", .{});
    interface.flush() catch |err| {
        _ = try output_file.write("Error?\n");
        return err;
    };
}
error: WriteFailed
/home/cleong/.zvm/0.15.2/lib/std/posix.zig:1376:22: 0x114800e in writev (std.zig)
            .BADF => return error.NotOpenForWriting, // Can be a race condition.
                     ^
/home/cleong/.zvm/0.15.2/lib/std/fs/File.zig:1760:21: 0x11445ba in drain (std.zig)
                    return error.WriteFailed;
                    ^
/home/cleong/.zvm/0.15.2/lib/std/Io/Writer.zig:316:28: 0x1046a99 in defaultFlush (std.zig)
    while (w.end != 0) _ = try drainFn(w, &.{""}, 1);
                           ^
/home/cleong/.zvm/0.15.2/lib/std/Io/Writer.zig:310:5: 0x109f2e3 in flush (std.zig)
    return w.vtable.flush(w);
    ^
/home/cleong/Desktop/cow.zig:17:9: 0x113d6fd in main (cow.zig)
        return err;

The content of hello.txt contains the following afterward:

Error?

replace it with

const interface = &output_file.writer(&buffer).interface;

interface is a field in Writer used with @fieldParentPtr so you can’t copy it out of writer.

1 Like

That’s also wrong, and would be a compile error, as you’re still throwing away the File.Writer (more complete explanation here).

Correct version:

var file_writer = output_file.writer(&buffer);
const interface = &file_writer.interface;

Relevant thread:

1 Like

true. For some reason I thought it was already assigned) Need to be more careful when responding. One time when I didn’t compile program to verify its correct was the time it is incorrect :frowning:

2 Likes

Pointing out, you don’t need a buffer, unless you are doing multiple small writes.

If you don’t use a buffer, then you don’t need to flush.

TBH, you don’t even need to use the writer interface.

Boy, that’s some seriously messed up design.

It’s a fairly common way to do interfaces, all styles have the same foot guns, but this style in particular is easier to get hit with them for sure.

add safety checks for pointer casting · Issue #2414 · ziglang/zig · GitHub will add safety checks for this, and other pointer casts. There are some other relevant and accepted proposals that I can’t find rn.

1 Like

The foot guns could be easily prevented by making retrieval of the interface happen through a function. Yet this is not done for some unknown reason.

3 Likes

which could maybe make also getting the interface one line shorter

const interface = output_file.writer(&buffer).interface();

No the File.Writer still needs a var, otherwise it is a temporary which is const, even if interface was a method it would need a *File.Writer as self, the benefit of it being a method would be that you get a compile error. (which isn’t being done because other features are planned to make it a compile error)

But couldn’t the self of the method be *const File.Writer to make it work in that case? The method would just return a pointer to the interface, so having a *const as self would be legit I think. If the var is needed in the scope for other reasons then ok.

Then you would end up with *const Io.Writer, which you don’t want, you want both of them to be *T without const, they shouldn’t be const because they contain variables that will be mutated during the operations on the File.Writer and Io.Writer.

You don’t want *const File.Writer because then you don’t know whether you could reliably and safely get a non const pointer and you don’t want the File.Writer to be a const/temporary anyhow, because you don’t want the implementation methods of the interface which use @fieldParentPtr to get back to the *File.Writer to try and write to a constant/temporary instance (for example when updating the File.Writer.pos field), which would result in illegal behavior or segfaults.

But there are already countless topics about the Io.Reader/Writer interfaces and how they work, so I don’t really want to explain all of that again.

4 Likes

You’re right, sorry for insisting, I see know it can’t work (just tested it).

1 Like