Maybe this has been discussed already somewhere else but it’s difficult to search for given the symbols involved, so here goes: I suddenly realized a pattern
optional.?
ptr.*
But then there’s
try errorunion
and not
errorunion.!
Wouldn’t the .! be more consistent with the other two and avoid the use of parenthesis when chaining on a method after the value has been successfully unwrapped from the error union?
(try errorunion).foo(); // before
errorunion.!.foo(); // after
Yeah, I think the primary reason for it has been that error handling should be more verbose so as to be easier to spot in the code. But I am starting to think that .! instead of try would be kinda nice, too.
That’s a good reason indeed. So this would be once again a matter of weighing the tradeoffs: error handling visibility vs syntax consistency and ergonomics. It’s a tough call to make.
I think it’s designed explicitly to prevent chaining in order to break expressions where errors can occur.
Additionally, the best place where to handle an error is not necessarily where you want to use the non-error value. The dot bang syntax will tempt you into delaying the try logic until you need the non-error value.
Agreed, but this made me think some more, and I come to the realization thet try is actually not for handling an error “on the spot”, but rather to bubble it up so a higher level in the call stack handles it. In other words, a try is like saying “give me the value or bail out immediately”, which in my mind fits well into an expression chaining scenario.
catch on the other hand forces you to handle the error on the spot even though it’s often used as a provide default value on error mechanism when used without the error capture (or even also a bail out immediately mechanism too when used with return or unreachable). So in this sense your reasoning would better apply to catch than to try, right?
Maybe, but I think it still applies when you think about sections of a function that can fail vs sections that can’t.
Imagine a function as a sequence of statements that can either fail (x) or not (-).
Everything else being equal, I think having one critical section of a function past which nothing can fail is preferable to having trys sprinkled everywhere.
I see. And I think I found another reason why .! would be a problem: it doesn’t play well with errdefer (and specifically when needing to free on error).
Status quo:
const ptr = try allocator.create(Node);
errdefer allocator.destroy(ptr); // no leak on error
_ = try ptr.returnsPtrButCanFail();
with .!:
const ptr = allocator.create(Node).!.returnsPtrButCanFail().!; // leak on error
I guess that like in other languages (C++ I’m looking at you) you could have both options, recommending .! only be used when you don’t need errdefer but that doesn’t seem like tha Zig way of doing things. It’s better to have only one way of doing things; it’s a win for code clarity, simplified parsing, analyzing, etc.
Oh, that’s interesting, I didn’t know that. But I agree, it’s certainly not for Zig. Coding in Zig for me has always felt the best exactly because it’s offered only the leanest tools, that way forcing me to write minimal and readable code even though it sometimes took me a moment to see why that was. But it’s always so cool to realise that Zig already has the foundation of very thorough design decisions!