BTW, I have another interesting idea: lazy parameter evaluation, by annotating parameters with defer. For example, the x parameter will be always evaluated when the function f is called, even if f is annotated with inline.
const print = @import("std").debug.print;
inline fn f(b: bool, x: void) void {
if (b) return;
_ = x;
}
var c = true;
pub fn main() !void {
f(c, {
print("x is evaluated\n", .{});
});
}
Is it a good idea to support lazy evaluated parameters by annotating them with defer? like:
It does works for the other way around. It looks a bit cursed, but I can see there are good use cases like passing strings for formatting error messages.
Expression-level defer/errdefer donβt really make any sense to me. What would
// deferred initialization of local variable
const x = defer f();
or
// mixing eager and deferred evaluation
const result = f() + (defer (if (cond) g() else h()));
even mean?
defer is not like other classes of control flow. Constructs like if and returnjump to a destination, so they can reasonably be used within expressions. defer on the other hand modifies the destination of a jump. If if is like goto, defer is like βcomefromβ. What does it mean to jump to the middle of an expression located within a different statement? If the expression returns a value, such a jump would be completely nonsensical.
This would violate one of the most fundamental principles of Zigβs design, namely no hidden control flow. If Iβm reading code like foo(bar()), I can now no longer trust that bar will actually be called without explicitly reading the implementation of foo.
There was an old proposal, stack-capturing macros, that would let you achieve something similar using an inline anonymous function-like syntax, but this was decided to not be a good fit for the language and rejected.