Removed array multiplication from Zig

In Ziglings we have some exercises with array multiplication. Since it is removed from Zig, I switched to @splat(). But for exercise 005_arrays2 it doesn’t look nice.

old:

const bit_pattern = [_]u8{ 1, 0, 0, 1 } ** 3;

new:

const bit_pattern_unit = [_]u8{ 1, 0, 0, 1 };
const bit_pattern: [3 * bit_pattern_unit.len]u8 = @bitCast(@as([3][bit_pattern_unit.len]u8, @splat(bit_pattern_unit)));

But maybe someone has a better idea?

Since that exercise is (at least currently) about array operators, and that operator no longer exists, I would personally just remove it. Maybe add @splat to the built-ins exercise?

Zig also just removed the repeating patterns test: https://codeberg.org/ziglang/zig/commit/bb83883acd9dee65423e836729eb0242c5e218ca?files=doc/langref/test_arrays.zig

I will take that into consideration. In this exercise there is also the ++ operator, that is still available. The next problem I encountered is:

// Use the array repeat '**' operator to make "ha ha ha ".
const laugh = "ha " ** 3;

I’ll probably have to delete that, because I can’t think of a suitable replacement. It’s a shame; I always found these operators very elegant and can’t quite understand why they’re being discontinued.

I guess it’s for language simplicity. Until mlugg deletes ++ too, we can enjoy the fact that multiplication is just repeated addition :face_with_tongue:

    comptime var pattern: []const u8 = "";
    inline for (0..3) |_| pattern = pattern ++ "ha ";

…but probably best to leave it out entirely

I liked ** and ++ too…

I agree with the commenter in the GitHub issue thread that the old behavior of ** would better be moved to a built-in @repeat, and that scope creep of @splat is suboptimal.

Never much liked ** for array multiplication on the syntax level, though. So it’s harder to argue against its removal.

I too like the idea of @repeat and @concat, with the added benefit of being able to control formatting with trailing commas. But rarely needed so I think comptime blk’s are fine.

agree, when it works as const laught = @repeat(“ha”, 3);

Oh, just for sharing. I happened to come across this exercise two days ago. After knowing ** has been removed for only 20 hours, I repeatedly used ++ for three times manually for it.

And now, because I still regard built-in functions as magic, I’m considering whether using undefined with inline for is more explicit.

btw, I’m learning zig with ziglings, I find it so helpful. Thank you!!

Welcome!

Even common and core functionality uses builtin functions, such as casting (e.g. @intCast) and importing (i.e. @import), so hopefully that will help the feeling of them being magic to dissipate.

@splat provides a more ergonomic way to do what people were mostly using ** for. Features need to actively justify their existence in the language, and nobody piped up with a sufficiently convincing use case for **, so it got removed. Ziglings is designed to teach the language, so the exercises are small examples that don’t necessarily represent real usage. In this case, I think it’s reasonable to just remove this ziglings exercise.

++ is used extensively in real code, so I wouldn’t worry about its removal.

If someone needs this kind of thing, as a one off I would do:

const bit_pattern = blk: {
    const pattern = [_]u8{ 1, 0, 0, 1 };
    const pattern_2 = pattern ++ pattern;
    // repeat as many doublings as are needed.
    break :blk pattern ++ pattern_2; // binary of final number
};

and if for some reason your code actually needs to do this a lot, then a userland implementation is reasonable:

fn Repeat(T: type, count: comptime_int) type {
    const info = @typeInfo(T).array;
    return [count * info.len]info.child;
}

fn repeat(pattern: anytype, count: comptime_int) Repeat(@TypeOf(pattern), count) {
    if (count == 1) {
        return pattern;
    }
    const inner_pattern = repeat(pattern, count / 2);
    if (count & 1 == 1) {
        return inner_pattern ++ inner_pattern ++ pattern;
    }
    return inner_pattern ++ inner_pattern;
}

yep, therefore you can learn this:

    const count = 3;
    const bit_pattern_unit = [_]u8{ 'h', 'a', ' ' };
    const bit_pattern: [count * bit_pattern_unit.len]u8 = @bitCast(@as([count]@TypeOf(bit_pattern_unit), @splat(bit_pattern_unit)));
    std.debug.print("{s}\n", .{bit_pattern});

ha ha ha

Here’s what I would conisder the most preferable way:

test "**" {
	const laugh = comptime blk: {
		var laugh_inner: []const u8 = &.{};
		const ha = "ha ";
		for(0..3) |_| laugh_inner = laugh_inner ++ ha;
		break :blk laugh_inner;
	};
	try std.testing.expectEqualStrings(
		"ha ha ha ",
		laugh,
	);
}

I like to set the total size instead of pattern.len * count so I use this:

fn arrayFromPattern(comptime T: type, pattern: []const std.meta.Child(T)) T {
    var arr: T = undefined;
    var i: usize = 0;
    while (i < arr.len) : (i += pattern.len) {
        const len = @min(arr.len - i, pattern.len);
        @memcpy(arr[i..][0..len], pattern[0..len]);
    }
    return arr;
}

const arr = arrayFromPattern([5]i32, &.{ 1, 2, 3 }); // <-- {1, 2, 3, 1, 2}

// I would love this but there is no way to make this...
const arr: [5]i32 = arrayFromPattern(&.{ 1, 2, 3 }); 

I recently removed ** from dvui. Found it quite nice how it makes every instance much more readable.

There was just one use-case of actual repeating pattern in a test, which I solved with a comptime block similarly to other answers.

:slightly_smiling_face:

const count = 3;
const str = "ha ";
const bit_pattern: [count * str.len]u8 = @bitCast(@as([count][str.len]u8, @splat(@as([str.len]u8, str.*))));
fn repeatStr(comptime s: []const u8, comptime n: usize) []const u8 {
    const unit = @as([s.len]u8, s[0..s.len].*);
    const str: [n * s.len]u8 = @bitCast(@as([n][s.len]u8, @splat(unit)));
    return &str;
}

test "laugh" {
    try std.testing.expectEqualStrings(
        "ha ha ha ",
        repeatStr("ha ", 3),
    );
}
test "we have ** at home" {
    const actual: [8]u8 = std.simd.repeat(8, @as(@Vector(3, u8), "ha ".*));
    try std.testing.expectEqualStrings("ha ha ha", &actual);
}

I think this exercise should be removed or revised.

It all makes sense eventually, but hard to put together as a beginner. You’re using three builtins just to duplicate a string a few times. @as should be called @coerce and @bitCast as @typeCast (or just @cast) but that’s not a box I’m trying to open. It’s just non-obvious and confusing and easily generates as “why the hell is this so difficult??” even after solving it.

I think it completely breaks the flow of ziglings. :confused:

Quite shocked to see how elegant the SIMD solution is. Maybe it should be used instead.

I ran into this too while helping someone at a Zig Day. I’m guessing the exercise made sense when it was originally constructed, but now it has been “updated to latest zig” so many times that it should probably be re-evaluated (and probably just deleted since it’s not a language feature anymore).

You would expect simplifying the Zig language to cause Ziglings to also shrink, right?