For shaders, even though work is going on to add opaque types and/or assembly types, not having matrices as root types really does suck. It’s not just a question of a.matMul(b) vs a * b; but also important for layouts of buffers. swizzling is also important for vector types i.e vec.xyz; vec.xxyy; and many more.
To add a bit myself: A lot of math heavy code because VERY noisy and noise can make things less clear since you first need to mentally remove the noise from the code (or physically if it’s too much) to be able to understand it.
For example this:
const num_vec: @Vector(8, u32) = nums;
const mask = num_vec % @as(@Vector(8, u32), @splat(2)) != @as(@Vector(8, u32), @splat(0));
const extracted = @select(u32, mask, num_vec, @as(@Vector(8, u32), @splat(1)));
const modified = extracted - @as(@Vector(8, u32), @splat(1));
const finished = @select(u32, mask, modified, num_vec);
return finished;
This is noisy to a point where it’s annoying.
I would like to write this (technically even without the @splat, but I would be okay with it since it is understandable that you would want something to mark these operations), since the length is available from context:
const num_vec: @Vector(8, u32) = nums;
const mask = num_vec % @splat(2) != @splat(0);
const extracted = @select(u32, mask, num_vec, @splat(1));
const modified = extracted - @splat(1);
const finished = @select(u32, mask, modified, num_vec);
return finished;
Besides the line where finished is initialised, you need to first mentally remove a lot of stuff to be able to understand what’s going on, unlike with the second one.
But the compiler doesn’t want the second one, even if you would think that the compiler could know what is wanted.
I don’t care if I need to write a lot, but I care about lots of text if it starts to obscure what the point of the code is, like here.
Agreed SIMD code is horrible to write ATM.
I’d argued that for any op x op comptime_int should cast the comptime_int to the type of x.
Even more, for arithmetic expression the type hint should be apply to each member of the expression.
const res: i32 = x_u8 + y_u4 * z_i8 should do all computation using I32.
For SIMD this would also help a lot.
except that u48 cant safely be coerced to an i32, but for the rest i agree.
You can typedef a concrete ‘@Vector’ to smth like vec4 which eases some of the pain, but in general I 100% agree, Zig should have syntax for most vector and matrix operations (and a @Matrix() type to begin with), basically a full mapping of the Clang extended vector and matrix language extensions for C so that vector math Zig code could look similar to shading languages.
Oops, typo, fixed
Yep, and that’s even more important since Zig seems to want to position itself as a shading language too.
You can pre-declare your const splats here and use RLS to get rid of the @as() coercion:
const vec8_0s: @Vector(8, u32) = @splat(0);
const vec8_1s: @Vector(8, u32) = @splat(1);
const vec8_2s: @Vector(8, u32) = @splat(2);
const num_vec: @Vector(8, u32) = nums;
const mask = num_vec % vec8_2s != vec8_0s;
const extracted = @select(u32, mask, num_vec, vec8_1s);
const modified = extracted - vec8_1s;
return @select(u32, mask, modified, num_vec);
I do think that the compiler doesn’t really have an excuse to not be able to assume the result type of the splat here.
My guess is that from the compiler design standpoint, it’s not just about producing code that works and is type-safe (which is what C’s compilers seem to primarily focus on) but is also about making sure that if the users don’t know the types that are in play, it becomes a compile error that potentially prevents a runtime error from happening.
Whether the user explicitly coerces the type with @as() or uses RLS as I’ve done, the type of every operand is still clearly visible, locally and internally in the code. This means that if, for instance, you’re using a symbol you’ve imported from a library as an operand and you download a new version of the library where the type of this symbol has changed, this immediately becomes a compile error.
I think this is also part of why operator overloading isn’t a thing in Zig.
In C++, you might include a symbol from an external library with an overloaded operator.
In a future version of this library, this symbol might have a completely different type and behaviour, but as long as the signature of its overloaded operator stays the same, your code will most likely still compile, but with different behaviour at runtime.
This could be argued to be miraculous in its own way, but I think most people can see that Zig explicitly preventing this kind of thing is a deliberate device to make Zig code more maintainable.
While I do know of that, this is imo in the same indirection ballpark as discussed in “I think Torvalds would vibe with Zig” from a few days ago.
And as quite a few people think (and I agree), this makes the code less readable (after all, nobody would (hopefully) write const two: u32 = 2;).
I agree with that, I just don’t think this reasoning actually grasps here (side question: Is “grasp” here the correct word?).