Function that accepts array of any length - is it a thing?

I think it is fine to just use array: anytype and do some comptime type checking on that parameter in the function.

Sure some people tend to dislike anytype, but that is a whole other general issue, separate from whether the parameter is an array type or some other type, so I don’t think this topic has a more specific answer that applies to arrays specifically.

Because Zig doesn’t allow to define type constraints for parameter types, you have to either be more verbose and construct the type from multiple other parameters or use anytype and write type checking logic for that parameter yourself.

There are also a bunch of people who have used the return type expression to do that type checking, to basically create a userspace type checking library, however I don’t like how these libraries tend to replicate normal Zig type checking code by turning it into equivalent but unknown vocabulary that needs to be learned and thus creating additional burden for the reader.

I think it is better to use just normal Zig code with maybe a local helper, that is self describing through its name, so that the type-check is readable without any prior knowledge of an additional type checking library.

If you really care about the type of the parameter being described in something that shows up in for example Zls you can do something like this:

fn tableSum(
    /// 2D array, with f32 value: `[_][_]f32`
    array: anytype,
) result: {
    const msg = "expected 2D array, with f32 value: [_][_]f32";
    const T = @TypeOf(array);
    if (!isArray(T)) @compileError(msg);
    const C = std.meta.Elem(T);
    if (!isArray(C)) @compileError(msg);
    const V = std.meta.Elem(C);
    if (V != f32) @compileError(msg);
    break :result f32;
} {
    var sum: f32 = 0;
    for (&array) |inner| {
        inline for (inner) |value| sum += value;
    }
    return sum;
}

fn isArray(T: type) bool {
    return switch (@typeInfo(T)) {
        .array => true,
        else => false,
    };
}

Which shows up like this when writing:
Screenshot_2025-10-06_21-43-55
And like this when you vim.lsp.buf.hover it (I imagine that other editors show it similarly considering it seems to be a Zls feature):

There is no way where you can just use .{} (which isn’t an array literal btw), instead you can use a normal array literal specifying the types explicitly:

pub fn main() !void {
    const sum = tableSum([_][3]f32{
        @splat(1.0),
        @splat(2.0),
        @splat(3.0),
    });
    std.debug.print("sum: {}\n", .{sum});
}
const std = @import("std");

Personally I think this may actually be a good thing, because it makes it immediately obvious what the actual data passed to tableSum is and what the result of the @splats look like.

If you didn’t have to explicitly type it, you would first need to know how tableSum works / is defined. (If there was a way to make it work without specifying the type at the callsite)

It is also what the standard library does (using explicit array literals):


The answer to the topic question is, yes, pass it an array or array-literal.

The answer to the actual question:
Can I pass array expressions with inferred types and inferred length to this function?

is no, instead you have to specify the array type manually, because the function signature doesn’t contain enough information, to infer the type (even if complicated deep code analysis could figure it out (which Zig doesn’t want to do)).

Unless you do the other verbose variant:

2 Likes