Surprising(?) zig 0.14.0 incompatibility when initializing arrays of structs

Just stumbled over this in one of my Zig projects when initializing an array of structs with default values like this:

ghosts: [NumGhosts]Ghost = .{.{}} ** NumGhosts,

In 0.14.0 this now produces the compiler error expected type 'pacman.Ghost', found '@TypeOf(.{})'.

Replacing the initialization with @splat() works (and tbh is nicer):

ghosts: [NumGhosts]Ghost = @splat(.{}),

…but I’m surprised that the former version isn’t equivalent. Is it a bug, or expected?

Like this should still work:

ghosts = [1]Ghost{.{}} ** NumGhosts,
1 Like

TBH, I would prefer this to work :wink:

ghosts: [NumGhosts]Ghost = .{},

Yea, idk I like how @splat(.{}) communicates both array initialization as well as array element initialization, while just .{} conceals the latter.

Btw, @splat virtually rendered the ** array multiplication syntax redundant, I think. If so, I’d like for there to be one way of doing things.

2 Likes

Only thing I don’t like about @splat is that it looks like a workaround for missing language syntax. E.g. in my mind, every occurance of a @ builtin is a code smell :slight_smile:

2 Likes

This is a consequence of removing anonymous struct types (which confusingly does not refer to anonymous struct inits .{} but rather a special struct type with special rules).

Previously, if you assigned a struct or array initializer .{} to an untyped variable or result location, it was given a special type that allows for coercion to a different structurally compatible struct type, which allowed for code like this:

var x: i32 = 10;
var y: i32 = 20;
const MyStruct = struct { a: i32, b: i32 };
const a = .{ .a = x, .b = y };
// @TypeOf(a) is a special anonymous struct type
const b: MyStruct = a;

After removing this language feature, the last assignment is no longer legal, because @TypeOf(a) is now a regular struct { a: i32, b: i32 }.

Both before and after removal of anonymous struct types, if you assigned .{} to a location with a result type, it takes on that type, so something like ghosts: [3]Ghost = .{ x, y, z } is valid and works like how you would expect.

However, the ** operator does not provide a result type (this is also true for most other operators), so the right hand side of an assignment like ghosts: [NumGhosts]Ghost = .{.{}} ** NumGhosts yields [NumGhosts]struct {} and not [NumGhosts]Ghost.

Before 0.14, this yielded [NumGhosts]<special anonymous struct type>, which is why the code used to work but no longer does.

14 Likes

Well, builtins are meant to be used sparingly, so I’d say that rather builtin overuse is a code smell.

Thanks for the explanation @castholm

Really? Is there documentation about this?

What about all the trigonometry functions, the @min/@max, etc?

No, just an opinion. It’s never an overuse when you know what you’re doing. @branchHint is a good example.