Is it possible to make all control flows as expressions?

Now, it looks only defer/errdefer controls flows cannot be used as expressions.
If they can, then we can write code like:

fn f(out: *u32) !void {
    out.* = errdefer 0;
    return error.A;
}

[update]: I just realized this might be okay for defer but bad for errdefer, because errdefer needs an else branch. :smiley:

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:

fn f(b: bool, defer x: void) void {
    if (b) return;
    _ = x;
}

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.

pub fn main(_: std.process.Init) !void {
    var result: u32 = 0;
    f(&result) catch {};
    std.debug.print("Does it Work? {d}\n", .{result});
}

fn f(out: *u32) !void {
    errdefer {
        out.* = 69;
    }
    return error.A;
}
...\0.17.0 Experiments> zig build run
Does it Work? 69
1 Like

Is there anything wrong with errdefer out.* = 0;?

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 return jump 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.

4 Likes