Your baz example made me a bit curious because thereās not a lot we can do to observe the value of an error, but we can observe their addresses, which I hadnāt considered before.
const std = @import("std");
pub fn errAddr(what: []const u8, x: *const anyerror!u8) void {
_ = x.* catch 5;
std.debug.print("{s}:\t{*}\n", .{ what, x });
}
// inserts space so it lines up with errAddr() which inevitably outputs a
pub fn valAddr(what: []const u8, x: *const u8) void {
std.debug.print("{s}:\t{s}{*}\n", .{ what, [_]u8{' '} ** "anyerror!".len, x });
}
pub fn main() !void {
errAddr("static constant value", &42);
errAddr("static constant error", &error.A);
const x: error{foo}!u8 = error.foo;
const X: u8 = 42;
valAddr("named constant value", &X);
errAddr("named constant error", &x);
var y: error{foo}!u8 = 2;
errAddr("error{foo}!u8 with value", &y);
valAddr("address of that value ", &try y);
std.debug.print("value error offset: \t\t{d}\n", .{@intFromPtr(&try y) - @intFromPtr(&y)});
y = error.foo;
errAddr("error{foo}!u8 with error", &y);
std.debug.print("sizeof( x): {d}\n", .{@sizeOf(@TypeOf(x))});
std.debug.print("sizeof(u8): {d}\n", .{@sizeOf(u8)});
}
$ zig run test.zig
static constant value: anyerror!u8@126f866
static constant error: anyerror!u8@126f89e
named constant value: u8@126f8dd
named constant error: anyerror!u8@126f8a4
error{foo}!u8 with value: anyerror!u8@7fff58866e34
address of that value : u8@7fff58866e36
value error offset: 2
error{foo}!u8 with error: anyerror!u8@7fff58866e34
sizeof( x): 4
sizeof(u8): 1
$ zig version
0.16.0
It seems zig really is true to its idea of treating errors as values, with the value of an error (if present) enclosed in the error itself.
pub fn bar(x: anyerror!u8) void {
_ = x; // error: error union is discarded
}
I feel like it all boils down to how permissive _ is. Itās weird for it to accept an error as a function parameter, but not assignment.
Personally, I think error union parameters of a function is not necessary to be handled in the function. It is the duty of its caller function to handle it. That means the bar function should also compile okay.
There is a difference between the way the thing is discarded in foo and bar.
In bar it is the general discard syntax. Itās a valid compilation error, because we (fortunately) arenāt able to just discard errors. They need to be explicitly discarded. The compiler could of course know that this directly comes from a function parameter instead of being the return value of a function, but at least to me this would be just an unnecessary special case that bloats the compilers code and will likely lead to bugs.
In foo the function parameter is discarded. This is different from above because there is just this case. Iād imagine it could be implemented in such a way that it doesnāt even look at the type, but this is just speculation.
I think baz should also be a compile error but maybe Iām missing something.
To stay true to Zigās Style Guide Iād suggest assert instead of check, and to follow the std naming expect instead of propagate.
Keep in mind that unreachable isnāt meant to be caught outside of safe builds. If you want an error to stop the process, use catch @panic("...") instead.
Though I appreciate the sentiment. It really does help when things are named well, rather than a gaggle of InternalStateManagerInterface() or puc_acquire or vsnprintf().
EDIT:
No, very specifically donāt do that! You will lose debugging information!
The āDoc comment guidanceā subsection is located under the section āStyle Guideā. I specifically linked to the subsection that suggest using assert as the vocabulary for checked illegal behavior.
I donāt know how youāre managing your namespaces, but if you actually encounter this case you can name it assertNoError instead.
You wonāt get debug information if you hit undefined behavior. If you want debug information AND catch the error in both safe and unsafe builds, youāll have to use std.debug.dumpErrorReturnTrace, thereās no way around it.
/// Returns the number of total elements which may be present before it is
/// no longer guaranteed that no allocations will be performed.
pub fn capacity(self: Self) Size {
return self.unmanaged.capacity();
}
A doc comment with āassumedā.
/// Delete the entry with key pointed to by key_ptr from the hash map.
/// key_ptr is assumed to be a valid pointer to a key that is present
/// in the hash map.
///
/// TODO: answer the question in these doc comments, does this
/// increase the unused capacity by one?
pub fn removeByPtr(self: *Self, key_ptr: *K) void {
self.unmanaged.removeByPtr(key_ptr);
}
A doc comment with āassertsā. Note the function name.
/// Asserts there is enough capacity to store the new key-value pair.
/// Clobbers any existing data. To detect if a put would clobber
/// existing data, see `getOrPutAssumeCapacity`.
pub fn putAssumeCapacity(self: *Self, key: K, value: V) void {
if (@sizeOf(Context) != 0)
@compileError("Cannot infer context " ++ @typeName(Context) ++ ", call putAssumeCapacityContext instead.");
return self.putAssumeCapacityContext(key, value, undefined);
}
Thank you for pointing it out. I donāt know how I never noticed. It makes absolutely no sense to me why those āassumeā functions are named in a manner completely contradictory with the documentation.