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

1 Like

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.

1 Like

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

3 Likes

I liked ** and ++ too…

6 Likes

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.

4 Likes

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.

3 Likes

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!!

7 Likes

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.

4 Likes

@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;
}
4 Likes

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

5 Likes

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,
	);
}
2 Likes

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 }); 
1 Like

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.*))));
1 Like
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),
    );
}
1 Like