Some cool language tricks

1. Figuring out if a value is comptime

pub inline fn comptimeKnown(val: anytype) bool {
    const Tuple = @TypeOf(.{val});
    return @typeInfo(Tuple).@"struct".field_attrs[0].@"comptime";
}

This one is pretty straightforward if you know how comptime fields work.

2. Figuring out if a value is comptime, part 2

comptime fields are being debated for removal. This absolutely devious workaround was posted by Fri3dNstuff, at https://codeberg.org/ziglang/zig/issues/32045#issuecomment-13664645.

pub inline fn comptimeKnown(x: anytype) bool {
    const pair: struct { @TypeOf(x), usize } = .{ x, 0 };
    return @TypeOf(" "[0..pair[1]]) == *const [0]u8;
}

How it works:

  1. A value must either be entirely comptime-known or entirely runtime-known. A value is comptime-known iff it depends solely on other comptime values. (Although note that comptime-ness gets lost at function call boundaries, which is why the one in the snippet is inline)
  2. The literal 0 is always comptime known. This means that the variable const pair = .{ x, 0 } is comptime-known iff iff x is comptime-known.
  3. When slicing arrays using array[start...end], if the start and end are both comptime known, the type of the expression is a pointer to an array (*const [end-start]T). the type of the slicing expression is []const T.
  4. If pair is comptime-known (which, again, depends solely whether x is comptime-known), pair[1] is also comptime-known, so the slicing expression " "[0..pair[1]] has a pointer-to-array type.
  5. Otherwise, pair[1] is only runtime known, so the slicing expression " "[0..pair[1]] has a slice type.

3. Pseudo @ResultType

Proposal: `@Result` to match cast builtins inference API · Issue #16313 · ziglang/zig · GitHub may have been shot down, but you can still provide much of its proposed utility (in certain situations) via the magic of decl literals!

For example, in a pub fn Vector3(T: type) struct:

pub fn splat(n: T) @This() {
    return .{ .x = n, .y = n, .z = n };
}
pub fn mul(self: @This(), factor: @This()) @This() {
    return .{
        .x = self.x * factor.x,
        .y = self.y * factor.y,
        .z = self.z * factor.z,
    };
}
pub fn floatCast(vec: anytype) @This() {
    return .{
        .x = @floatCast(vec.x),
        .y = @floatCast(vec.y),
        .z = @floatCast(vec.z),
    };
}

const foo: Vector3(f64) = ...;
const bar: Vector3(f32) = .mul(.floatCast(foo), .splat(1));

This leads to a kind of pseudo-Łukasiewicz notation I quite like.

4. std.array_hash_map

Not technically a language trick, but I wanted to include it anyways.
Are you tired of writing iterators like this?

var entries = hash_map.iter();
while (entries.next()) |entry| {
    const key = entry.key_ptr;
    const val = entry.value_ptr;
}

array_hash_map to the rescue! Since values are stored densely packed in a regular MultiArrayList, iterating becomes a breeze:

for (hash_map.keys(), hash_map.values()) |key, value| {}

It’s so nice! As an extra bonus, array_hash_map variants are Unmanaged by default.

3 Likes