Guard-Let-Catch (and If-Let-Catch) to avoid long (nested) do-blocks

I want to revive a discussion that was started 7 years ago in this thread. I find myself often writing long do blocks when calling into throwing APIs, effectively nesting much more code than actually needed, leading to the catch clause being very far away from the actual code that could have caused its entrance. For example:

func randomMovies(genre: Genre, count: Int) -> [Movie] {
   do {
      let movies = try Database.loadMovies(byGenre: genre)
      
      // no throwing API calls from here on...
      guard !movies.isEmpty else { return [] }

      var randomMovies: [Movie] = []

      for _ in 0..<count {
         randomMovies.append(movies.randomElement()!)
      }
   
      return randomMovies
   } catch {
      return []
   }
}

Likewise, I might want to provide different catch clauses for different throwing functions. Currently I have to provide nested do-catch statements to achieve that, which leads to less readable and less concise code very quickly.

I'm proposing to introduce a catch clause added to if or guard statements that is only usable and is even required if the if/guard condition includes a try call. For example, the above code could be written like this instead:

func randomMovies(genre: Genre, count: Int) -> [Movie] {
   guard let movies = try Database.loadMovies(byGenre: genre)
   catch { return [] }

   guard !movies.isEmpty else { return [] }

   var randomMovies: [Movie] = []

   for _ in 0..<count {
      randomMovies.append(movies.randomElement()!)
   }
   
   return randomMovies
}

This could also keep support for Optional types with else like suggested by Chris Lattner here:

I could also imagine that we keep the questionmark for try?, I've not thought about the syntax extensively. But I really want the advantages of a guard let for throwing APIs:

  1. Multiple of them with their own catch blocks like I can provide multiple guards with else blocks
  2. Avoid nesting for code that follows the guard as there's no throwing API involved there anymore

What do you think? Or was there any reason this was not further elaborated?

17 Likes