A few notes on your code, there are some things that can be done in a simpler ways, if you wish to pursuit Zig further:
// this has got to be the fugliest way of initializing an array
// who thought this was better than memset()? seriously.
var readbuf = [_]u8{0} ** READBUF_LEN;
This used to be a common pattern, but Zig has a better alternative now:
var readbuf: [READBUF_LEN]u8 = @splat(0);
// not as bad as Haskell but still this is a lot of hoops to jump through
// just to set up to print something to stdout. You know, it's either one line
// in C (printf) or one line in C++ (cout). At least they're not making you
// learn about Monads. I remember Java was absolutely horrible at this sort
// of thing. The bar has been set low over the years. Rust is *slightly*
// more straightforward than this but not by much
var buf: [1024]u8 = undefined;
var stdout = std.fs.File.stdout().writer(&buf).interface;
Zig has some helper functions for this. For a larger application I’d recommend to look at std.log, for debugging purposes there is also std.debug.print
// this is the equivalent of checking for null. fugly.
if (ctx_maybe) |_| {} else {
return HashError.CreateHashEngineAllocFail;
}
That is one way to do it, but I rarely use this option. Instead the orelse keyword is a much more convenient alternative in this use-case:
// Look, the pointer is longer optional, no need for .? everywhere
// ↓
const ctx_maybe: *openssl.EVP_MD_CTX = openssl.EVP_MD_CTX_new() orelse return error.CreateHashEngineAllocFail;
errdefer { openssl.EVP_MD_CTX_free(ctx_maybe); ctx_maybe = null; }
Setting the pointer to null is unnecessary. The function is left afterwards, there is no code that even could access it.
Also I really do not understand why you chose to convert the pointer to usize in the end. Generally that shouldn’t be necessary.
the std panic() call fails to actually panic.
This is most likely a bug and should be reported so it can be fixed.