multidimensional-arrayliteralszig

How to define a comptime array of arrays of divergent size


I am very new to zig and doing a very simple coding exercise. The question is about scoring words in scrabble and presents a table. As a sort of challenge I wanted to replicate the scoring table as an array of arrays where the index of the outer array represent the points for a letter and the inner arrays contain the letters that match that score. Then I wanted to convert this to a lookup table (really just a flat array of scores) in comptime. I also wanted to be able to change the table without having to change any of the conversion code.

I'm currently having difficulty with the very initial part, declaring the multi dimensional input array. I've tried this:

const point_letters = [_][_]u8 {
    [_]u8{'A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T',},
    [_]u8{'D', 'G',},
    [_]u8{'B', 'C', 'M', 'P',},
    [_]u8{'F', 'H', 'V', 'W', 'Y',},
    [_]u8{'K',},
    [_]u8{},
    [_]u8{},
    [_]u8{'J', 'X',},
    [_]u8{},
    [_]u8{'Q', 'Z',},
};

as well as

const point_letters = [_](*[_]u8){
    ([_]u8{'A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T',}).*,
    ([_]u8{'D', 'G',}).*,
    ([_]u8{'B', 'C', 'M', 'P',}).*,
    ([_]u8{'F', 'H', 'V', 'W', 'Y',}).*,
    ([_]u8{'K',}).*,
    ([_]u8{}).*,
    ([_]u8{}).*,
    ([_]u8{'J', 'X',}).*,
    ([_]u8{}).*,
    ([_]u8{'Q', 'Z',}).*,
};

But they both give the same error pointing at the size for the inner arrays:

error: unable to infer array size


Solution

  • Arrays in Zig are homogeneous, i.e. all elements have the same type.

    Since the length of an array is part of its type, trying to create an array containing arrays of different lengths is not possible.

    What you can do is create an array containing slices, because a slice consists of a pointer (pointing to an array stored elsewhere) and an integer, i.e. all slices have the same type.

    So first of all you need to change the type of point_letters to [_][]u8.

    Then, to construct the slices, you have to use &[_]u8{ … } instead of [_]u8{ … }.

    In addition, the compiler (version 0.11.0) told me that @constCast is required (after trying @ptrCast first in order to fix the error when using &[_]u8{ … } alone).

    This works:

    const point_letters = [_][]u8 {
        @constCast(&[_]u8{'A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T',}),
        @constCast(&[_]u8{'D', 'G',}),
        // …
    };
    

    To make it less verbose, you can use a helper function that is executed at compile time:

    fn slice(comptime a: anytype) []u8 {
        return @constCast(&a);
    }
    
    const point_letters = [_][]u8 {
        slice([_]u8{'A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T',}),
        slice([_]u8{'D', 'G',}),
        // …
    };
    

    I just found out that if you change the type of point_letters to [_][]const u8 (taking jjmerelo's answer as a hint), no cast is needed:

    const point_letters = [_][]const u8 {
        &[_]u8{'A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T',},
        &[_]u8{'D', 'G',},
        // …
    };
    

    (And at this point it becomes clear that for the special case of u8 as the base type, &[_]u8{'A', 'E', …} is just the same as "AEIOULNRST".)