[swift-evolution] do try catch?

Liam Butler-Lawrence liamdunn at me.com
Mon Dec 7 09:24:33 CST 2015


I completely agree Wei. Using try as a keyword placed before every throwing function is one of the greatest strengths of Swift’s error-handling model.

I do have a new proposal regarding do/try/catch (if I should post this in a new thread, please let me know):

————

The problem:

In Swift 1.0, the only way to conditionally unwrap an optional was with if let. This ended up causing the “pyramid of doom” so often that in Swift 2.0, guard let was introduced.

The same problem is present with do/try/catch. Consider this code snippet:

let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

do {
    
    let documentsDirectoryURL = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
    
    let persistentStoreFileName = "CoreDataStore.sqlite"
    let persistentStoreURL = documentsDirectoryURL.URLByAppendingPathComponent(persistentStoreFileName)
    
    do {
        try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: persistentStoreURL, options: nil)
    }
    catch AnotherError.Case1 {
        
    }
    catch AnotherError.Case2 {
        
    }
    catch AnotherError.Case3 {
        
    }
    catch {
        
    }

}
catch MyError.Case1 {
    
}
catch MyError.Case2 {
    
}
catch MyError.Case3 {
    
}
catch {
    
}

First, yes, I know that URLForDirectory() doesn’t throw MyError. I’m illustrating the common pattern using actual function calls.

I see two problems with the above code:

1. The standard execution of code (if no errors are thrown) gets more and more nested with every call to a throwing function, causing a pyramid of doom. There’s only two levels in this example, but it could easily be four or five.

2. The first try NSFileManager.default… is no less than 20 lines away from its catch statement. Again, with more try statements, this number only increases, making the code very unreadable in my opinion.


A possible solution:

Allow a catch clause to be used directly after any function that throws. This clause includes all the catch x {} catch y {} error-handling code. Just like the else clause in a guard let, this catch clause has to return or otherwise break normal execution. Because of this, the overarching do {} catch {} blocks are no longer needed, and normal execution continues without continually increasing code indentation.

This is how the above code snippet might look if this solution was implemented:

let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

let documentsDirectoryURL = try NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false) catch {
    catch MyError.Case1 {
        
    }
    catch MyError.Case2 {
        
    }
    catch MyError.Case3 {
        
    }
    catch {
        
    }
}
let persistentStoreFileName = "CoreDataStore.sqlite"
let persistentStoreURL = documentsDirectoryURL.URLByAppendingPathComponent(persistentStoreFileName)

try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: persistentStoreURL, options: nil) catch {
    catch AnotherError.Case1 {
        
    }
    catch AnotherError.Case2 {
        
    }
    catch AnotherError.Case3 {
        
    }
    catch {
        
    }
}

The syntax of the new catch block could certainly be improved on (it might also benefit from typed throwing as discussed in another thread). However, even imperfect, this code is much more readable than what’s currently required, as one can instantly tell which try statement goes with which catch statements. In addition, the the pyramid of doom is gone, and the resulting code is reminiscent of a function with many guard lets.

If you have any feedback or know of potential issues that I’m not aware of, I look forward to hearing from you. Thanks so much!

Liam

> On Dec 7, 2015, at 9:36 AM, 王巍 via swift-evolution <swift-evolution at swift.org> wrote:
> 
> The first (current) choice is better in fact.
> 
> With try closure out of the scope, you could not tell exactly which statement would throw. 
> In your example, only `makeSandwich` would throw, so there is no need to mark `eatASandwich` with a `try`.
> 
> Current design follows minimal conception very well, you could get to know that only the `makeASandwich` would throw an error, which reduce a lot of noise when reading this code.
> 
> 
> Best regards.
> ---
> Sincerely, 
> Wei Wang (王巍, @onevcat)
> im.onevcat.com <http://im.onevcat.com/>
>> 在 2015年12月7日,下午11:30,Don Arnel via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> 写道:
>> 
>> Is there a reason this format was adopted for error handling:
>> 
>> do {
>>     try makeASandwich()
>>     eatASandwich()
>> } catch Error.OutOfCleanDishes {
>>     washDishes()
>> } catch Error.MissingIngredients(let ingredients) {
>>     buyGroceries(ingredients)
>> }
>> 
>> rather than this format:
>> 
>> try {
>>     makeASandwich()
>>     eatASandwich()
>> } catch Error.OutOfCleanDishes {
>>     washDishes()
>> } catch Error.MissingIngredients(let ingredients) {
>>     buyGroceries(ingredients)
>> }
>> 
>> The second format is much more intuitive, and reads easier IMO.
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151207/72556a68/attachment.html>


More information about the swift-evolution mailing list