`for try! await`

Found something unexpected:

func load(url: URL) async {
    for try! await byte in url.resourceBytes { // πŸ›‘ Expected pattern
    }
}

I expected try! to work here (as it is in other places).

BTW, it works here, but here try is placed differently:

func values() throws -> [Int] { [1, 2, 3] }

func theTest() {
    for value in try! values() {} // βœ…
}
3 Likes

AFAIR this is a missing feature indeed.

Same bug if you use try?, too. Weird.

try? makes a little more sense [edit: to leave out], since it’s not clear what would happen on nil.

(Also, try? probably shouldn’t have been added to the language in the first place.)

Perhaps, but it could simply mean that value is Optional<Int>. Makes sense and could be useful (maybe whatever you're doing with it in the loop body isn't merely unwrapping it, but passing it onwards as-is to something that takes an optional.

Strongly disagree. What constitutes an unexpected error is subjective and context-dependent, so there's no way to design a 'correct' API that uses throws vs nil appropriately.

It's also super handy for prototyping and scripting, where you just want to gracefully fail if something throws rather than crashing (try!) or being forced to implement a bunch of do-catch boilerplate.

2 Likes

For the record "try?" doesn't work in this simpler example:

for value in try? values() {} // πŸ›‘ For-in loop requires '[Int]?' to conform to 

As for the try? itself – I think it's fine, using it every now and then.

The issue why this wasn't added was because it (at the time) became slightly ambiguous about what the try? or try! would do per ending. There was one interpretation that try? would "swallow the error" and stop the loop, where as the other version of try? would continue iterating past the failure. However that design was refined with an amendment to SE-0298 such that it would be defined as always terminal once a terminal event occurs; e.g. it would return nil from the iterator past a throw by rules of what implementations should be expected to do.

This would mean that if we were to pitch this adjustment (which I think is worthwhile). The code-generation would be:

for try? await item in seq { }
// expands to
var $iterator = seq.makeAsyncIterator()
while let item = try? await $iterator.next() { }

likewise the try! would expand:

for try! await item in seq { }
// expands to
var $iterator = seq.makeAsyncIterator()
while let item = try! await $iterator.next() { }

It is worth noting that functions or properties that return an AsyncSequence themselves may be asynchronous or throwing so there are very valid cases of:

for try await item in try await buildSequence() { }

The advocating of the for try? await syntax does not mean I have any real opinion or not of for value in try? values { }. That can of worms is asking if Optional is really a collection-of-one-or-zero. Which I would suggest that if anyone pitches for try? await they should steer away from answering that or having a concrete tied-to-the-pitch/proposal opinion about.

6 Likes