[dead end] Await-compatible for loop syntax by adding another EXPR

The await syntax struggles with fitting the for loop in any way. I think I have a diagnosis why, and a potential solution. solution still needs work.

The core of the problem is that the current for loop syntax is:

for PATTERN in EXPR {
}

and awaiting (and error handling with ?) is something that applies only to expressions, not patterns. The only EXPR that happens to be in the for loop is not the one we want awaiting/error handling to apply to.

So all the syntaxes struggle, because there’s literally no place for them. The only current options are to use expression-specific syntax as if it was a pattern (crates syntactical special cases), apply expression syntax to some implied/imaginary expression, or put it in a place where it looks like it applies to the wrong expression (either the stream or the for loop as a whole).

So the solution is to put another EXPR in the for loop synatax.

Currently Rust has two places where PATTERN and EXPR work together:

if/while let PATTERN = EXPR {}

and

match … {
    PATTERN => EXPR
}

The difference is that in let the EXPR is evaluated first, then assigned to the pattern. In match the PATTERN is evaluated first, then used in EXPR.

(I’m using try!() instead of any of the await syntaxes to avoid suggesting any particular choice)

for let x = try!(/*how do you refer to an element here???*/) in stream {}
for x => try!(x) in stream {
  /* how to refer to the result of try!(x)??? */    
}

but both are still missing having a second pattern or some other way to refer to the missing element.

However, maybe that’s still a step forward, because presence of the second EXPR makes the for loop syntax orthogonal with other features. It’s automatically compatible with any await syntax, any error handling syntax, user macros, etc.

1 Like

Would this be equivalent to the following existing alternative? Is it sufficiently better to justify the syntax?

- for x => try!(x) in stream {
+ for x in stream {
+     let x = try!(x);

One thing I find confusing in x => try!(x) is that the first occurrence of x has two different types – at first it is the element type of the stream, but then it it is the return type of the rhs of =>. The existing way with for and let makes it clear that the same name is rebound to a potentially different type.

3 Likes

Argh, indeed. That’s a fatal flaw that I overlooked.

So in the end the things involved are:

  1. EXPR for the stream
  2. PATTERN to name an element of the stream
  3. EXPR to apply await or ? to the result of 2.
  4. PATTERN to name the result of 3.

At this point we have four elements to cram into the syntax. That’s quite tricky without something being hidden/implicit or some of the elements confusingly merged together :frowning:

  • Without 2: for let item = try!($0) in result_iter (using Swift’s magic arg $0)
  • Without 3: that’s for await? x design space explored so far
  • Without 4: doesn’t work
  • Conflating 2 and 4: for x => try!(x) in result_iter { x }
  • Conflating 2 and 3: for try!(x) in result_iter also evaluated so far
  • Conflating 3 and 4: doesn’t work

So digging deeper, the explicit version would need all 4 elements. It could be something like this:

for let PATTERN = EXPR for PATTERN in EXPR {
}

for let item = try!(res) for res in result_iter {
}

As I try to make every item explicit without any magic/ambiguity, the existing two-line pattern starts to look quite reasonable:

for res in result_iter {
    let item = try!(res);
}
1 Like

I’m not sure how much I like this idea, but, what about allowing for .. in to be stacked, like you can do in Python’s generator expressions?

    for value in result? for result in list_of_results {
        ...
    }

but also

    for cell in row for row in matrix {
        ...
    }

and similar.

To make this work as smoothly as possible, we would probably want any not-otherwise-iterable T to be coercible to an iterator over an imaginary list of one T, which sounds like it could have surprising consequences, but I dunno, maybe it wouldn’t be too bad?

I’d prefer nested iteration in for loops with a syntax like this:

for row in matrix
for cell in row?
{
    ...
}

This way it’s easier to follow nested iteration, and it translates much more easily to nested for loops if you need them.

This:

for row in matrix
for cell in row?
{
    ...
}

Can be translated to this:

for row in matrix{
    for cell in row?{
        ...
    }
}
1 Like

This would allow error handling without explicit nested loops:

for cell in row? for row in matrix {
    ...
}

or with anything: for cell in row.reversed() for row in matrix.inverse()? {...}

@asa-z

I think nesting is necessary here:

for row in matrix
for cell in row?
{
    ...
}

This is more straightforward to read,and has a trivial translation to the old style.

I don’t see adding single-line for in for in as being a good idea,it encourages difficult to read code.

When I read your first post, I thought you wrote that adding the try operator to row required moving to explicitly nested loops. Looking back I see I misread.

I just want to note that this doesn’t work for the titular case of driving a stream. A stream isn’t an Iterator of Futures, which is why it needs special syntax such that the process of getting each element is the await-like loop-and-poll dance.

So I’d argue that

isn’t the wrong position. (This only then becomes problematic if for loops return a value in the future (generators integrated with for loops), when the operation could potentially apply to that value.)

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.