For try loops

Has there ever been discussion of for try loops?

for try x in foo.to_str()?.chars().next()?.foo()? {
  // ...
}

As sugar for:

for x in y {
    try {
        let x = foo.to_str()?.chars().next()?.foo()?;
        ...
    }
}

I know the try fn has been thrown around before.

1 Like

for + await loops have been discussed, and no consensus emerged (awaiting a future is kinda similar to unwrapping a result). In for <pattern> in <expr> there's not much room to insert any new action that isn't ambiguous or confusing with either patterns or expressions.

I'm not sure what you mean by for x in try, as AFAIK that would iterate over Result which is an odd quirk that just visits the single Ok value. Do you mean loops with let x = x?; pattern?

I don't know if there has been such discussion. But since try {} isn't even stable yet I don't know if it makes sense to start discussing such sugar.

Furthermore, that desurgaring isn't even that useful since it only saves you two characters.

I would have thought that for try x in y would have skipped the iterations that would break.

Namely something like:

for x in y {
    try {
        let x = x?;
        ...
    }
}

Oh yes, sorry, that's what I meant, I'll update the post. for await was left as a future extension in the async/await rfc, meant for streams/async iterators.

I guess for try x could be ambiguous. Does it return on the first error, or does it act more like filter_map? I think the former is more logical.

Saving characters is a weaker goal than saving indentation...

It seems similar with previously discussed for match syntax.

I don't think ignoring the error case is something we should encourage.

5 Likes

What are y and foo.to_str()?.chars().next()?.foo()??

1 Like

Sorry for the confusion, this is the desugaring I had imagined - early return on the first error:

for try x in iter {
  // ...
}

for x in iter {
   let x = x?;
   // ...
}

You try to get the next item in each iteration.

How does this compare to if-let (uhh while-let?) chains?

A problem with this proposal is that a impl Iterator<Item=Result<_, _>> type suggests that the iterator may continue yielding elements after an Err(_) is returned, but this syntax leaves little room for processing them. If the iterator type clearly communicated that no further elements can be returned after an Err, this would not be such a problem.

A while ago, I came up with this design, as part of a larger proposal (first sketched in this thread), but it could be adopted mostly independently of the larger one.

Can you early return with a while let?

Thinking about this more, maybe it's not the intended, only, or best design for a for try loop. What about allowing ? to be used as a pattern? The following would accomplish the same thing and is pretty straightforward:

for x? in iter { ... }

It could also be used in match patterns:

fn foo(foo: Option<Result<String, usize>>) -> Result<String, usize> {
   match foo {
      Some(x?) => Ok(x),
      None => unreachable!()
   }
}

assert_eq!(foo(Some(Err(5))), Err(5))
assert_eq!(foo(Some(Ok("x".into()))), Ok(5))

What stops you from using these?

loop {
  let foo = bar.next()?;
}

while let foo = bar.next()? {
}

// etc

Is that an argument against for loops altogether?

1 Like

Yeah?

The only loop improvement we can think of is something that lets you both iterate and apply stuff to the value, i.e. sugar for the following:

for [name] in [iterator] {
  let [name] = [operations];
}

but we can't imagine such a thing being a good fit for rust.

We can imagine something like these, but we think they look kinda weird:

for x become x? in iterator {
}

for x become foo(x) in iterator {
}

// etc

mostly because they're declaring two bindings, but using the same identifier token (i.e. the literal token, in the source file) for both. Nevertheless, these are basically like using .map(|x| x...) but without introducing a closure scope.

You could argue this has the advantage of keeping everything in the same line, but we see that as a disadvantage because we actually try to keep to 80 columns. Additionally, this still introduces a depth of indentation, so... we don't see what you're trying to gain from this? What do you see that we don't see?

let mut iter = ...;
while let Some(item) = iter.next().transpose()? {
  ...
}

I have used this pattern a few times where it ends up cleaner than doing let item = item?; inside a normal for loop.

2 Likes

No, it's an argument against heavily non-obvious syntactic sugar that doesn't accomplish much in terms of expressive power. The already-existing alternatives are all trivial — so there isn't a lot of value in adding this kind of nee syntax.

Well the author said it was, so...

Either way, I no longer think for try is a good idea, but for x? may have some merit.