Problem with using [N]u8 as a buffer

This is a problem from the Exercism site. I’m pretty new to Zig, and I’ve been through most of rustlings. I thought I understood slices and arrays well enough, but I’m sort of stuck.

The task is to print out a rhyme, that I suppose was going to be stored in a [4000]u8 buffer. I figured that it might be useful to use std.fmt.bufPrint. The function below does NOT solve the problem - but it gives me an error that I don’t understand how to fix (surely, because I don’t really understand passing buffers as arguments yet):

pub fn recite(buffer: []u8, start_verse: u32, end_verse: u32) []const u8 {
    var ind = start_verse;
    try std.fmt.bufPrint(buffer, "This is {}", .{nouns[ind - 1]});
    while (ind < end_verse) : (ind += 1) {
        std.fmt.bufPrint(buffer, "that {}", .{verbs[ind], .nouns[ind - 1]});
    }

    std.fmt.bufPrint(buffer, ".", .{});
    return buffer;
}

The error I get back is:

house.zig:33:5: error: expected type '[]const u8', found 'error{NoSpaceLeft}'
house.zig:31:63: note: function cannot return an error
/opt/zig/lib/std/fmt.zig:632:21: error: cannot format slice without a specifier (i.e. {s} or {any})

N.B.: line 33 is the try statement.

The compiler is complaining about the return type of your function recite.

The return type is []const u8, but whenever you use try in a function you are saying that the function can return an error, so your return type should be ![]const u8 or error{NoSpaceLeft}![]const u8

Alternatively you could use a catch clause and handle the error.

2 Likes

I thought the error{NoSpaceLeft} meant that I had somehow filled up the array? I don’t think I’m allowed to change the return type of the function …

There are a couple problems with the code you posted:

  1. The return type of recite precludes the ability to return an error.
pub fn recite(buffer: []u8, start_verse: u32, end_verse: u32) []const u8 {

In the body of the function you call a std.fmt.bufPrint which can fail. You attempted to get around that by using a try, but try will automatically return an error from your function, but you can’t do that because the return type is only []const u8. Rather than using try, you will need to use catch if you are not allowed to change the return type of the function.

something like

std.fmt.bufPrint(buffer, "This is {}", .{nouns[ind -1]}) catch { return buffer; };
  1. You are not handling errors that are possible in additional places (2 other calls to std.fmt.bufPrint.

  2. Your while loop isn’t working right, Calls to std.fmt.bufPrint do not move a “seek” pointer, so you are going to end up overwriting your buffer on every run, which I don’t think is the intention. You’ll need to manually handle the empty space. Luckly std.fmt.bufPrint returns a slice that just contains the data that was written, and so you can see how much was written by using .len field on the slice

    var seek_idx = 0;
    while (ind < end_verse) : (ind += 1) {
        temp = std.fmt.bufPrint(buffer[seek_idx..], "that {}", .{verbs[ind], .nouns[ind - 1]}) catch { return buffer; };
        seek_idx += temp.len;
    }
  1. I don’t think that format string will work, you are passing in two values to be formatted, but only have a placeholder for one.
2 Likes

Thanks - I just cheated and looked up someone elses solution. I’m only doing this for me … just a bit demoralizing to have that as the problem recommended after “hello world.” :frowning:

do you mean ziglings/exercises: Learn the ⚡Zig programming language by fixing tiny broken programs. - Codeberg.org ?

Ah, yes. s/rustlings/ziglings/
And spent decades doing Python and not enough time managing memory.

From what I can tell of how other people solved the problem, the error that could have been thrown from std.fmt.bufPrint would have been caught with catch unreachable;. The other interesting thing I saw was:

    var fbs = std.io.fixedBufferStream(buffer);
    const writer = fbs.writer();

catch unreachable is declaring to the compiler that you are guaranteeing there wont be an error, in Debug and ReleaseSafe build modes your program will panic if it encounters something declared to be unreachable.
In ReleaseFast or ReleaseSmall build modes the compiler will use the declaration that the branch is unreachable to do optimisations, if an error is encounter then what happens next is undefined behaviour meaning anything could happen, i.e. you are lucky if it crashes.

TLDR: dont use catch unreachable unless you can prove that an error will never be encountered, if you intend to panic on an error you should do so explicity ie catch @panic("panic message")

6 Likes

Got it - that sounds like a bad habit I would like to avoid learning early on, thanks.

2 Likes