Search code examples
zig

How to create a Struct with an array-typed field in Zig?


As part of testing my solution to Advent Of Code 2024 day 5, I'm trying to create a struct containing an array created from an literal, but I'm getting an error that I can't resolve:

const std = @import("std");
const print = std.debug.print;

const Update = struct { values: []u32 };

test "Create and operate on an Update" {
    const update = Update{ .values = [3]u32{ 1, 2, 3 } };
    print("DEBUG - update is {}\n", .{update});
}
$ zig test scratch.zig
scratch.zig:7:40: error: array literal requires address-of operator (&) to coerce to slice type '[]u32'
    const update = Update{ .values = [3]u32{ 1, 2, 3 } };
                           ~~~~~~~~~~~~^~~~~~~~~~~~~~~

That seems clear enough - except that when I follow the advice, I get a type-mismatch:

const update = Update{ .values = &[3]u32{ 1, 2, 3 } };
$ zig test scratch.zig
scratch.zig:7:29: error: expected type '[]u32', found '*const [3]u32'
    const update = Update{ .values = &[3]u32{ 1, 2, 3 } };
                           ~^~~~~~~~~~~~~~~~~~~~~~~~~~~

That makes sense - so far as I can understand, & converts a variable to a pointer, so it's correct for the compiler to complain about being passed a *[]u32 when it expects a []u32. So, both options give me an error - how can I pass a []u32 to the Update constructor?

After reading David Vandersons Zig notes, I think I have a better idea of what's going on:

  • []u32 is a slice of u32s, not an array thereof.
    • Which is correct for what I want - because an array must have a comptime-defined size, which would imply that all Updates much have values of the same size.
  • So I need to instantiate an Update like so:
const Update = struct { values: []u32 };

test "Create and operate on an Update" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var values = try allocator.alloc(u32, 3);
    defer allocator.free(values);
    values[0] = 1;
    values[1] = 2;
    values[2] = 3;
    const update = Update{ .values = values };
    print("DEBUG - update is {}\n", .{update});
}

Which works as-expected. Is this the idiomatic way to do this? I'm surprised there's no built-in way to define a "slice-literal" as there is for arrays - 5+n lines to define an n-length slice (or 3+n lines if you already have an allocator available) seems verbose, to me!


Solution

  • [(number here)]u32 are arrays, but []u32 are slices (fat pointers).

    If you update your struct to use an array of 3 elements ([3]u32), then your test code will work correctly.

    If you had an immutable slice ([]const u32), you could use this syntax: &.{ 1, 2, 3 }. The documentation lists it as "alternative initialization using result location".

    But since you need the slice to be mutable, you need to allocate memory for it. Hence using an allocator is the right approach here.