std.io.GenericWriter.print doesn't format output properly

Newbie here,

I’m facing an issue with the below code and I’m not sure is it a bug in the language or am I missing something.

const std = @import("std");

pub fn main() !void {
    var counter: u8 = 0;
    while (counter < 2) : (counter += 1) {
        try println(intToStr(counter));
    }
}

fn println(str: []const u8) !void {
    const stdout = std.io.getStdOut().writer();
    // try std.fmt.format(stdout, "{any}\n", .{str});
    // this is using format inside
    try stdout.print("{u}\n", .{str});
}

/// Calculate the number of digits required for a given integer type
fn maxDigitsForType(comptime T: type) comptime_int {
    return std.math.log10(std.math.maxInt(T)) + 1;
}

pub fn intToStr(n: usize) []const u8 {
    var buffer: [maxDigitsForType(@TypeOf(n))]u8 = undefined;
    // NOTE: If you want to make the buffer c-compatible you could use bufPrintZ instead
    const result = std.fmt.bufPrintZ(buffer[0..], "{d}", .{n}) catch unreachable;
    return @as([]const u8, result);
}

when I try to print using try stdout.print("{u}\n", .{str}); I get a very weird behaviour:
{ ý }
{ ý }
wassou@was-pc:~/Projects/zig/bb_testing$ ./main
{ þ }
{ þ }
wassou@was-pc:~/Projects/zig/bb_testing$ ./main
{ ÿ }
{ ÿ }

everytime I execute I get different output.

but when I use std.fmt.format directly:

fn println(str: []const u8) !void {
    const stdout = std.io.getStdOut().writer();
    try std.fmt.format(stdout, "{u}\n", .{str});
    // this is using format inside
    // try stdout.print("{u}\n", .{str});
}

I get the expected output:

{ 0 }
{ 1 }
wassou@was-pc:~/Projects/zig/bb_testing$ ./main
{ 0 }
{ 1 }

Hello @hrvatska93
Welcome to ziggit :slight_smile:

To print a string, the correct format specifier is s:

fn println(str: []const u8) !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("{s}\n", .{str});
}

Function intToStr returns a string placed in stack that is overridden.

pub fn intToStr(n: usize) []const u8 {
    var buffer: [maxDigitsForType(@TypeOf(n))]u8 = undefined;
    const result = std.fmt.bufPrintZ(buffer[0..], "{d}", .{n}) catch unreachable;
    return @as([]const u8, result);
}

buffer is allocated in stack and its lifetime ends when intToStr returns.
result is a slice that points in buffer and inherits its lifetime.
When intToStr returns, its return value is overridden by other variables.

3 Likes

Hi @dimdin , thanks for the reply, yes I realized the problem was that I’m making a temporary and referencing it later after free. :sweat_smile:

but I don’t understand why std.fmt.format still works

1 Like

When println is called, the string is still valid; the address of intToStr on stack is replaced by the address of println.

print implementation calls format but it is indirectly invoked from a function pointer.

Build settings (optimization level) can change the behavior, both might fail or both might succeed.

It is undefined behavior and the result can be anything :slight_smile:

1 Like

got it Thanks!

I have created buffer on main and passed it as a slice and now it works as expected,


pub fn main() !void {
    var counter: u8 = 0;
    const size = maxDigitsForType(@TypeOf(counter));
    var buffer: [size]u8 = undefined;
    while (counter < 2) : (counter += 1) {
        try println(intToStr(&buffer, counter));
    }
1 Like