Hello everyone!
Today I found out that there is no closure/arrow function in Zig. I found some proposals, all closed, but It’s very difficult for me from an external point-of-view to understand if there is or not the intention to include closures in Zig.
You’ve probably gathered that it’s a contentious issue because of Zig’s explicit allocation model. Anonymous functions in other languages can be stateful, so we can talk about the “capture” of an anonymous function. Stateless anonymous functions are fairly easy to solve but are verbose (there’s examples in that link I sent you).
One last point about the language here - closures are used quite a lot in Zig (specifically if you are talking about type-erased callbacks). From your original post, it looks like what you’re referring to is a terse-syntax for defining function objects.
Yes, I was thinking about a more compact syntax, but the link you gave me sheds much light on why it is like it is right now and how to do classical closures in Zig.
After reading it, it makes sense after all!
It looks like you’ve got a small bug in your program:
const len = text.len;
...
const last = if (this.end > len) len else end;
return text[first .. last + 1];
If last is equal to text.len, then this will read one index beyond the end of the string.
I’d recommend changing those lines to:
const last = @min(this.end + 1, text.len);
return text[first..last];
Personally, I’d just keep it to non-inclusive ranges to be consistent, but I get that you’re trying to mimic another function
I think that the example/template, however, is more complex than it needs to be. For instance, we don’t actually need the ClosureScope object. You can pass the integers directly into the function as comptime arguments:
I bring this up not to disparage your article (it’s well written and well intended). I think it’s introducing more steps than are actually necessary.
I also think you should point out that this is for comptime-known parameters. This won’t work for runtime closures (which are doable, but there’s more machinery involved).
Good point about the ranges. I was trying to keep things simple and not to distract from the pattern being presented. The article had already been updated in the Summary section about why it was presented in its verbose form and I supplied a similar simplified example similar to yours. The article also had been updated to remove the comptime restriction and the newly presented pattern works for compile or run time values. Thanks for the feedback, it was appreciated.
The comptime restriction is still there in your latest version. It is still generating a new function for every call to ClosureFunc and all parameters must be comptime known.
So, technically, it’s still not a closure. It’s a function template/macro.
The current version as of today works with both compile and runtime values. Pull the example code from the article and run the tests. If there is still some additional use case not covered, then I would welcome a test that demonstrates the issue so I can understand the shortcoming. BTW, in the current version ClosureFunc has been renamed ClosureGenerator to better reflect its functionality and ClosureBind has been renamed ClosureBinder to better reflect its functionality.
Sorry, my previous comment was not quite correct. It’s not still comptime only.
But, it works by storing the bound variables in a struct scoped variable (also called static scope or global scope in other languages). This is not how closures are expected to store their state.
You can break the test by adding _ = TextInRange(0, 0); on line 96, overwriting the previously declared closure’s state. I’m guessing this is not the behaviour you’re expecting.
I think the confusion comes from struct scoped variable in the anonymous struct in ClosureBinder. If you change that to a struct member (this: ClosureScope = undefined, without the var and with a comma) you’ll see that your example can’t compile.