fwiw, here’s how I would do it:
pub fn logU32ln(v: u32) void {
std.debug.print("0x", .{});
for (0..8) |i| {
const shift: u5 = @intCast((7 - i) * 4);
const nibble: u8 = @as(u4, @truncate(v >> shift));
if (nibble < 10) {
std.debug.print("{c}", .{'0' + nibble});
} else {
std.debug.print("{c}", .{'a' + nibble - 10});
}
}
std.debug.print("\n", .{});
}
(I’ve replaced your log functions with std.debug.print, just for my own convenience when I gave this a quick test)
In particular, your @truncate
s are slightly odd/not-best-practice – they tell the compiler to throw away any data “above” the truncate. Contrast: the @intCast
in const shift: u5 = @intCast((7 - i) * 4);
instead tells the compiler “this is purely a type change, this operation won’t lose any data” and then the compiler will holler if you were wrong about that, which is nice. (you might consider a while loop instead – you’re correct that for-loops are usize-only, and only increasing)
Your code works as-is, but the nuances of the different conversion builtins lets the compiler help you out more in case you made a bad assumption, or the code changes later, etc. The point of the verbosity is to squash bugs.
It’s a bit of a headache and it’s pedantic for sure, but it fits zig zen
:
- Communicate intent precisely.
- Edge cases matter.
- Favor reading code over writing code.
…
(you’re not alone in thinking it’s a bit awkward, and that may improve in the future. e.g. see Zig and Emulators and Short math notation, casting, clarity of math expressions - #38 by andrewrk)