0

I wanted to write fizzbuzz, but instead of just printing, store it in an array.

The problem is converting the numbers to strings. I first tried with bufPrint, but It outputed white-space instead of a number. Then I tried out with allocPrint, it compiles the app, but after entering the number crashes, with weird error messages in std/fmt. I tried printing the output of allocPrint, it outputs correctly, but I can't add it to the array.

After much more, I found out that the type that allocPrint produces is a string of type []u8, but my type is []const u8. How can I turn the output of the function to []const u8?

The askUser function works correctly & produces a u64, the problem is not related to it. Also, what is the best or most common string type in zig?

code

const std = @import("std");

fn fizzBuzz(x: usize) !std.ArrayListAligned([]const u8, null) {
    const allocator = std.heap.page_allocator;

    var array = std.ArrayList([]const u8).init(allocator);

    var i: usize = 0;
    while (i != x) : (i += 1) {
        const string = try std.fmt.allocPrint(allocator, "{d}", .{i + 1});
        defer allocator.free(string);

        try array.append(if (((i + 1) % 3 == 0) and ((i + 1) % 5 == 0)) "FizzBuzz"
            else if ((i + 1) % 3 == 0) "Fizz"
            else if ((i + 1) % 5 == 0) "Buzz" else string);
    }

    return array;
}

pub fn main() !void {
    const count = try askUser("! ");
    var x = try fizzBuzz(@intCast(usize, count));
    defer x.deinit();

    defer std.debug.print("\n", .{});
    for (x.items) |y| {
        std.debug.print("{s} ", .{y});
    }
}

1 Answers1

1

The std.ArrayList([]const u8) doesn't take ownership of the strings you put into it. It only takes slices (which is a pointer + length). So, when you deallocate the strings in the loop you invalidate the slices that you just added into the array list.
Simply removing defer allocator.free(string); makes your program print a correct result.

How to convert []u8 to []const u8 in Zig?

No need to do anything, the type coercion happens implicitly.

sigod
  • 3,514
  • 2
  • 21
  • 44
  • wow thanks! it now works, but is it safe? – KianFakheriAghdam Nov 22 '22 at 13:43
  • Well, technically you're leaking memory of all those strings (as after `x.deinit()` they won't be accessible anymore). `fizzBuzz` should accept an `Allocator` that it should use for both the array list and strings. – sigod Nov 22 '22 at 14:23
  • And before you `deinit` the array list, you should iterate over all items and free each. Or, even better, if you make a separate allocator for `fizzBuzz` call (e.g. `var arena = std.ArenaAllocator.init(std.heap.page_allocator); var allocator = arena.allocator();`), you can deinit the entire allocator in a single `deinit` call after you're finished with the array list. – sigod Nov 22 '22 at 14:23
  • Also, you know the size of the array upfront, so you should use `std.ArrayList([]const u8).initCapacity(allocator, x)`. – sigod Nov 22 '22 at 14:24
  • That's kind of confusing, Am I not already using a single allocator? Which is that page one. Forgive me, but I am new & I don't know about this `arena` thing. – KianFakheriAghdam Nov 22 '22 at 15:29
  • Yes, but right now you have no way to control which allocator `fizzBuzz` will use. Technically, you could access `x.allocator` after `fizzBuzz` call, but that seems more of a hack. Instead, you can update the function like this: `fn fizzBuzz(allocator: std.mem.Allocator, x: usize): ...` – sigod Nov 22 '22 at 16:19
  • Unfortunately, the [documentation](https://ziglang.org/documentation/0.10.0/std/) right now is incomplete. The current [recommendation](https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code) is to read the source code of the standard library directly. But you can read a bit about allocators [here](https://ziglearn.org/chapter-2/). – sigod Nov 22 '22 at 16:19
  • Actually I think I got it. I have a parameter of `allocator: std.mem.Allocator` and I use that as the allocator for both the `ArrayList` and `allocPrint`. I created an `arena` of type `page_allocator` with a `defer arena.deinit()` and passed `arena.allocator` as the parameter. – KianFakheriAghdam Nov 22 '22 at 16:33
  • Also I ran a test with it, using the `std.testing.allocator` and I had no memory leaks. =] – KianFakheriAghdam Nov 22 '22 at 16:35