-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce expression-scoped variables #8019
Comments
I think this is pretty verbose compared to the C equivalent for loop, but I would certainly accept it to solve the leaking-into-parent-scope problem. It's also more general, which is good.
Is there a technical reason why it's limited to only a single declaration? It's not uncommon to do things like |
No, that is just a limitation to restrict the use of this feature. You can always fall back to normal named blocks
Yeah, that's why i made a proper proposal out of this. I think it could work out and fit the language, but i'm not 100% sure |
It's the best fix i've seen for the problem yet and I like that it's general too, it's pretty elegant in my opinion |
I think allowing only declarations is the correct restriction, allowing multiple declarations makes it more useful without encouraging misuse. |
That's the most likely outcome :) But try doing that for nested loops - nobody does it, because it's too ugly even when formatted properly (and these bugs tend to happen after changes where scoping wasn't originally required) |
What about this one? Pretty straight forward let x: usize = 0, y: usize = 0 in <...> Or reuse var x: usize = 0 in while(x < 10) : (i += 1) {
std.log.info("counter = {d}", .{ i });
} |
I am with @uael on this, most programmers are more familiar with I suppose |
I really like this idea and I think with(<expr>) |<name>| { ... } looks more in line with current syntax than with(var <name> = <expr>) { ... } |
This with (const T = []const [*:0]makeACoolType(a, doSomething(y)))
fn foo(a: anytype, b: anytype, c: T, z: T) T {
...
} |
@cryptocode You could always do this: with (var a = 2) with (var b = 3) ... With @czrptr's suggestion it would look like this: with (2) |a| with (3) |b| ... |
@marler8997 yeah that's more than good enough if there are reasons not to allow the (to me) more natural: with (var a=2, var b=3) ... (still only declarations) |
@czrptr, Edit: The alternative syntax also doesn't allow a distinction between |
@zzyxyzz |
I'm for the fengb variant with (.{ .a = @as(u32, 5), b = "str"}) |*data| while (data.a > 0) : (data.a -= 1) {
std.log.err("{s}: {d}", .{data.str, data.a});
} After writing that out I'm a bit meh about it, just trying to keep the number of syntax rules in my brain down by trying to make it similar to other patterns we already have. |
@mattnite, with (@as(i32, 0)) |*i| while (i.* < 10) : (i.* += 1)
std.log.info("counter = {d}", .{ i.* }); to {var i: i32 = 0; while (i < 10) : (i += 1)
std.log.info("counter = {d}", .{ i });
} or even {
var i: i32 = 0;
while (i < 10) : (i += 1)
std.log.info("counter = {d}", .{ i });
} let alone C's for (int i = 0; i < 10; ++i)
std.log.info("counter = {d}", .{ i }); // taking liberties here or D's foreach (i; 0 .. 10)
std.log.info("counter = {d}", .{ i }); // ... and here My 2¢. |
If we squint our eyes and twist our brains we can kind of massage an // this is just sugar to make any value optional so we can use it inside an `if` statement
fn with(x: anytype) ?@TypeOf(x) {
return x;
}
pub fn main() !void {
if (with(@as(usize, 0))) |*i| while (i.* < 10) : (i.* += 1) {
@import("std").log.info("counter = {d}", .{ i.* });
};
} One big difference with this is that Things brings up another question, maybe our var optional_value : ?usize = 0;
if (optional_value) |var value| {
// value is a mutable copy of the payload of `optional_value`
value += 1; // OK, doesn't affect 'optional_value'
} Maybe this is an area that should see some exploration. I haven't seen much on it, has anyone else? Taking it a step further would allow an explicit type to be declared: if (opt_val) |var val : usize| {
} |
Interesting idea, but I'm afraid that if (with(@as(usize, 0))) |*i| while (i.* <= 10) : (i.* += 1) {
std.debug.print("{}\n", .{ i.* });
} is only the second-coolest way ever of printing the numbers from 0 to 10. The gold medal still belongs to brainfuck, even though the solution is considerably shorter:
|
This construct would require a new keyword and context-sensitive declaration parsing, for what is essentially syntax sugar that might save three lines, at best. I'm not a big fan of it. |
If you could add several names to a declaration, with something like On the other hand, this would not solve the issue of declaring both |
Another small note concerning notation: The "let/var/const ... in" variation has the problem that you could do things like that: var a = const b = var c = const d = foo() in d in c in b; which is equivalent to
, but allowing this in Zig would be problematic. I really think that the original |
I love this feature, thanks for the comprehensive proposal! I've wanted to propose something like the An alternative is
My original motivation was from before return location semantics so I wanted an explicit way to initialize a struct field to point to another field. It also enabled the kind of "anonymous block" that was proposed before and rejected because named return blocks are sufficient.
as an alternative to
This would be the use-case for the optional init expression, but we could require |
I am also a fan of short variable names for complicated or repeated expressions. Often my capture variables are just single letters because it's obvious what it refers to. It's a double edged sword - less noise is easier to read, but you lose semantic hints from the names. This could be a nice way of creating scopes for temporary aliases. It can also be an interesting way to create "contexts" if we want the expression-statement ability without breaking from a block by adding a
Which I guess would be the equivalent of something like
|
I dont like to enable squeezing too much stuff on one line, because it hurts readability (to me)a lot. It also reduces editing/refactoring simplicity, because you cant just move or copy the One nice thing about zig is for example that loops require an explicit annotation of the loop precondition, so there is less clutter on one line to search. |
An additional reason for the 'why?' of this is that indentation levels are a limited resource, and the current idiom for while iterators consumes one simply for variable initialisation. Given existing code, I'm surprised that no one has any examples that keep the
The last example usage "Explicit variable scope" is, I think, quite useful for explicit resource management if
This might(?) also apply to some |
While this might be less cumbersome than another scope, it is still strictly more typing than leaking the index. It’s the right direction, but it doesn’t move the point of minimum effort, so it’s not a solution. Also, the example puts one line strictly within the scope of the previous line, with no indentation. There was at one point a compile error proposed for that kind of thing, because it will be missed easily when skimming. The solution applied in the rest of the language for such cases is indentation, which is a key thing that this proposal aims to prevent. I really don’t think this is worth the effort. |
I suggest
Another possibility, of course, is to do what C++ and other languages have done and allow putting declarations inside of constructs that start a scope, e.g., |
In Pascal and Nix this I believe the main problem with it is that at the beginning you think this construction will be used only near your "small" block where you control everything, but after that someone starts making some really crazy things by using this Otherwise if this is going to be accepted I'd propose to rename it to something less confusing like PS. Sorry if this comment makes no sens in context of Zig, I don't have much experience and just digging through different proposals |
I would like to add that the C# language does something similar via the |
Introduce expression-scoped variables
Addresses #5070 and kinda addresses #358 as well.
Idea
The core idea of this proposal is to introduce a new expression type
with
:The
with
expression allows a in-expression declaration of a scoped variable. The code would be equivalentto
The idea of this is to create a way to declare variables that are explicitly scoped just to be used in a single expression or statement like most counter variables in a
while
orfor
loop.Syntax
with
follows the same syntax rules aswhile
orif
, allowing top-level use as well as expression use:Semantics
with
accepts a single variable declaration and publishes the declared variable into the expression block.The declared variable will not be visible outside the scope of
with
, rendering this code impossible:The result of
with
will be the result of the enclosed expression and will be passed through.with
will not have any effect on the returned value.Usage Examples
Encapsulating a counter variable:
Encapsulating an iterator state:
Encapsulating iterator state inside of a expression:
Minimal scope for dummy variables:
I know this example isn't a good showcase, but there is enough real-world C code that requires some pointers to be passed in even with dummy variables and
with
allows to reduce the scope those dummies will spill to the absolute minimum.Inline-reuse of return values:
Explicit variable scope:
Why?
with
is meant to be more ergonomic than introducing a block that just encapsulates a single variable that might even need a label, break statement and such.Spilling variables into the outer scope introduces bugs (missed variable re-initialization, usage of wrong loop variables) which are hard to detect.
Most people won't introduce a new scope as it both looks weird (personal taste) and is hard to type, thus will mostly be left out and spills variables.
It also communicates the idea that the encapsulated variable is meant to be scoped into the expression or block
Variants
Using capture syntax instead of declaration syntax
This would affect the syntax and would use elements from other parts of the language
Thanks to @fengb for proposing this
Abusecases
This section lists stuff how this feature can be abused
Replacement for
@as
This one is kinda absurd, but it would work with the proposed version:
Nested
with
sThis is something that could happen
The text was updated successfully, but these errors were encountered: