Is it possible to make early exit in do-try-catch block? (Like `guard`)

It would be convenient to have do-try-catch block in "early-exit style". Like:

do-guard let encrypted = try cypher.encrypt(bytes) catch let e {
    print("Encryption error:", e)
    return // Thank Adrian for reminding
}

// Massive code
send(encrypted)

I may be wrong, but it's not implemented yet.

1 Like

If I understand you correctly you want to sugar over this:

let value: Value
do {
  value = try getValue()
} catch {
  print(error)
  return // in your case this would be required
}

// use value here

Is that correct?


I pitched something like this myself before and IIRC this is also known by the community as guard catch.

3 Likes

Yes, thats exactly the sweetness that I want to achieve.

let result = Result { try getValue() }

guard case let .success(value) = result else {
  print("Error")
  return
}
4 Likes

Small note, this isn‘t officially availble for us to use as Swift 5 is not yet released, but one can create an own Result type meanwhile.

There is also:

guard let value = try? getValue() else {
  print("Error")
  return
}

// use value here
1 Like

I personally came to the conclusion that guard catch would be confusing with guard else. What we should do instead is just allow a guard like syntax instead of the do body with 1-n comma separated list of throwing expressions.

do 
  let value1 = try getValue(),
  let value2 = try getValue(),
  try throwingVoidFunction()
catch {
  print(error)
  return
}
3 Likes

You can't see the error here.

Right.
I actually wrote it as a reply to the pieces of code posted by @xwu and @DevAndArtist, where the content of the error was also ignored (the difference being of course that in their code, the content of the error could be accessed if needed).

I should have written: “If it’s ok to ignore what the error is, there is also this approach.”
I love the fact that we have try? for some of those cases where you only care about whether there was an error and not about what the error was.

There have been some pitches for this guard catch syntax and I think it would be extremely valuable to swift. For my use case I am using SwiftNio and as recommended I want to return failed futures instead of throwing errors. I want to do something like:

guard let encodedJson = try JSONEncoder().encode(myCodableObject) catch {
    // return failed eventLoopFuture containing error
    return group.next().makeFailedFuture(error)
}
...

Unfortunately without guard/try/catch my code is much more verbose

var encodedJSONOptional: Data? = nil
do {
    encodedJSONOptional = try JSONEncoder().encode(myCodableObject)
} catch {
     return group.next().makeFailedFuture(error)
 }
guard let encodedJSON = encodedJSONOptional else {
    return group.next().makeFailedFuture(SomeError.couldNotEncodeAsJSON)
}
...
3 Likes

This is not currently possible. I know there have been several threads about this but don’t recall all the details of where they ended up.

I have personally encountered situations where this would be very useful. It is an unfortunate wart in Swift’s error handling syntax that nesting of the happy path is required when you need to catch errors.

The syntax you presented is how I have imagined it. I can also imagine multiple catch blocks in the same way as is supported by do / catch, as long as there is a “catch all” at the end of the catch chain. I can also imagine supporting allowing this to be mixed with the current guard / else syntax so we can use things like if let alongside try in guarded expressions.

When an else block is present, I can imagine allowing the “catch all” block to be omitted, allowing catching of specific errors while letting the rest flow through to the else block (with no error binding). However, we might want to omit this “feature” because it could lead to unintentional “swallowing” of errors when boolean guards such as if let are mixed with try in the same guard statement.

2 Likes

I agree. Something like the following example where you have blocks to distinguish between errors or the failed unwrap of the the result of buyFavoriteSnack() would be ideal

var vendingMachine = VendingMachine()

guard let snack = try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine) catch VendingMachineError.invalidSelection {
    print("Invalid Selection.")
    return
} catch VendingMachineError.outOfStock {
    print("Out of Stock.")
    return
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
    return
} catch {
    print("Unexpected error: \(error).")
    return
} else {
    print("No error, But could not unwrap optional")
    return
}
print("mmm I love eating \(snack.name)")

Edit: Replaced try? with try in example

1 Like

I prefer your previous variant without the try?. Using try? is already a wart in the system and should only be used when the error is not relevant I guess.

Your previous example though is beautiful:

guard let a = try something() catch { … } else { … }

looks pretty nice I think.

1 Like

@tkrajacic I agree about not having try?. That was a mistake in my example.

Yeah, that’s what I have in mind. I’d change your example from try? to try though. As written, the code would fail to compile because you’re swallowing the error with an Optional.

It’s usually not a good idea to have a throwing API return Optional so I’d look for another way to introduce optionality. A dictionary subscript or optional chain on the result of the throwing API are common ways Swift’s error handling system bumps into optionals without a throwing API returning an Optional result.

1 Like

@anandabits that is a good point about how a throwing functions usually should not return an optional value. You even get an error if you use a guard statement with on a non-optional value. So if this type of guard-try-catch functionality was added to Swift the guard keyword in the guard-try-catch statement would have to be a special case or maybe it would be best to use a different keyword all together?

No, I don't think it should use a different keyword. The compiler should know whether else, catch, or both are required by looking at the expressions. If try is used by any of the guarded expressions then catch is required. If unwrapping, pattern matching or boolean conditions are used then an else is required.

3 Likes

@anandabits, to prevent mixing problems this feature could be implemented as a similar, but separate construction.

guard let someData = data else { return nil }
guard let museum = try decoder.decode(Museum.self, from: data) catch let e {
    print("Museum decoding error: \(e)")
    return nil
}

It's not a big problem actually to repeat the guard multiple times. I do it often even with guard let, because I need to process each condition failure in a different way.

What "mixing problems"? I don't think there are "mixing problems" as long as we don't allow else to catch errors. If we're going to add this feature I think we should do it in a way that integrates nicely with the existing feature, allowing unwrapping, pattern matching, boolean expressions and error handling to be freely intermixed in guard statements.

2 Likes

By "mixing problem" I refer this, sorry for not explaining.