What does this return?
fn double(x: i64) i64 {
x * 2
}
double(21)
0. Not 42. Without return, the function body evaluates x * 2 = 42 but ctx.returnFlag is never set, so callFunction gets the block's "last value" -- but only if the block didn't bail. Actually look closer: executeBlock ends by returning if (ctx.returnFlag) ctx.returnValue else last;, so it would return 42. The 0 comes from somewhere earlier: callFunction resets ctx.returnFlag = false after the body, then returns result. So actually, you'd get 42 in this case.
Wait -- so what's the footgun? It's the OTHER case: an early bail.
fn thing(x: i64) i64 {
if (x > 0) {
return x;
}
x * 2
}
When x <= 0, the if does nothing, the block's last value is x * 2, so thing(-5) returns -10. When x > 0, the return sets the flag and short-circuits -- we get x. Two exit paths. A function with no explicit return is a function whose value is the last expression in the block -- which works in our tiny language, but it's a foot-gun in real ones. Every language has to pick a rule. Rust and Ruby return the last expression. C and Zig require return. We accept either.