Non-error fixed buffer printing

I have a fixed buffer (i.e. [128]u8) that I want to print into. I could use std.fmt.bufPrint or Writer.print, however, these will error if what I want to print is over the buffer length. Normally, that’s a good thing. However, for my use case, I want to fill up the buffer as much as possible, all the way to the length and stop writing at that point and return.
What is the best way to do this?

var buf: [128]u8 = undefined;

// Some writer setup or something
var writer: ???;

writer.print("App - {s}", .{some_runtime_known_string});
//Use written
test "buffer is filled to the end" {
    var buffer: [16]u8 = undefined;
    var writer = std.Io.Writer.fixed(&buffer);
    const actual = if (writer.print("{s}", .{"hello" ** 4}))
        writer.buffered()
    else |_|
        &buffer;
    try std.testing.expectEqualStrings("hellohellohelloh", actual);
}

1 Like

An Io.Writer.fixed should work I think. Internally it uses fixedDrain which has this to say:

/// When this function is called it usually means the buffer got full, so it's
/// time to return an error. However, we still need to make sure all of the
/// available buffer has been filled. Also, it may be called from `flush` in
/// which case it should return successfully.
pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { ... }

So you would get and error, while still having the buffer filled.

4 Likes

why not just catch |err| if (err != error.WriteFailed) return err?
the written will be buf

ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ

2 Likes

Now that I think about it, you can, if you already have a writer, wrap it in a limited one. That should likely also work

you are correct that the interface will not write to the buffer if there is not enough space, instead lowering to the implementation to deal with both the interfaces buffer and the to-be-written data.

However, the Writer.fixed impelementation accounts for that, ensuring that the buffer is actually full before returning the error.

/// Writes to `buffer` and returns `error.WriteFailed` when it is full.
pub fn fixed(buffer: []u8) Writer {
[...]
/// When this function is called it usually means the buffer got full, so it's
/// time to return an error. However, we still need to make sure all of the
/// available buffer has been filled. Also, it may be called from `flush` in
/// which case it should return successfully.
pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
[...]
1 Like

how about a wrapper that does something like this? how about this?

const std = @import("std");

pub fn main() !void {
    var buf: [8]u8 = undefined;
    var w = std.Io.Writer.fixed(&buf);
    var prev_end: usize = 0;

    while (true) {
        prev_end = w.end; // save the end before writing

        w.print("{s}\n", .{"hello"}) catch |err| {
            std.log.err("{t}", .{err});
            break;
        };
    }

    std.debug.print("{s}", .{buf[0..prev_end]}); // hello
}

there’s also std.fmt.count but that’d be wasteful in this case