Not sure why you jumped from a quit: bool field to returning ?void, when you could also just return a bool.
I would argue that returning a bool is better, as it indicates a logical condition, as opposed to optionals which indicate the presence or absence of a thing, in this case the presence or absence of nothing lol.
By the way the same trick also works with errors.
Either way I would say go with the type that best describes what you want to do, and a ?void is kind of the least descriptive option. An error{BreakRecursion}!void would be better in my opinion, since you can at least give it a name while having similar semantics.
The thing is: during recursion I do not want to return values. That is why I reinvented the boolean
There are some other conditions inside the recursion where I simply want to exit the function.
In these cases I would have to return some meaningless value.
Errors work the same way yes… But that would feel even more weird. It is not an error.
I agree it is weird and maybe not incredibly clear, but quite convenient.
const STOP: ?void = null;
fn gen(...) ?void
{
while (true)
{
if (not_found) return; // meaningless value
go_on(...) orelse return STOP;
}
}
fn go_on(...) ?void
{
if (no_more) return; // meaningless value
store(...) orelse return STOP;
gen(...) orelse return STOP;
}
fn store(...) ?void
{
// store something.
if (some_condition) return STOP;
// no return value needed
}
Impossible that is. The recursive functions contain comptime parameters. I would have to use a stack etc. In reality the code is much more complex depending heavily on the comptimes and 5 other parameters.
(edit: although I would like very much to have it non-recursive. did not dare to try it yet!)
If you’re using the return value of {}/null for control flow at the call site, it doesn’t seem like the return value is meaningless . I think it’s just a matter of encoding this meaning as a bool instead of as a ?void. Still an interesting trick though!
I kinda like it tbh, because it’s clearer (to me at least) that the null return value is the exception and means “stop”, while with a boolean it’s unclear whether the ‘exceptional’ return value is true or false (e.g. does return value true mean “please continue” or “please stop”).
I don’t think it is impossible, whatever you have implemented the comptime parameters don’t exist at run time, so it should be possible to transform the code, but depending on how complex your code is, it might be difficult.
Still transforming the code might be worth it and could even make it more clear (not necessarily, but possible) what is going on. You also could have a hybrid, where some parts work via recursion on some via labeled switch.
I would personally always favor writing code in whichever way induces the least cognitive load on the reader, but out of all the alternatives I think this suggestion hits the sweet spot between “convenient and concise to write” and “easy to understand”:
fn gen(...) error{Aborted}!void
{
while (true)
{
if (not_found) return;
try go_on(...);
}
}
fn go_on(...) error{Aborted}!void
{
if (no_more) return;
try store(...);
try gen(...);
}
fn store(...) error{Aborted}!void
{
// store something.
if (some_condition) return error.Aborted;
// no return value needed
}
I like this actually. The call site can’t ignore the null arm, so it’s about the caller.
I’m interpreting your example as returning null to mean “no side effect occurred”, and void aka not returning to mean “did side effects”. Which is kind of elegant, in my opinion.
The one thing here is that ?void is a confusing signature to encounter, without an explanation of why it’s being used. But that just means adding a comment about what’s happening.
Exactly. And inside the functions we can just return {nothing void}, which is very convenient in my complicated case. I just implemented it and works like a charm.
Long live the new boolean.