init? vs init throws

Now that we have some proper error handling measures, I've been having a bit of a rough time deciding between init? and init() throws.

What I've come to realize is, my problem is really about the callsite, I actually always want to use throws. The simple fact is that I can return more information about the failure and let the caller decide if they care about it or not.

Here's an API that currently uses init?() that I think would actually value from providing the failure reason instead of just a blanket nil.

/// Construct from an ASCII representation in the given `radix`.
///
/// If `text` does not match the regular expression
/// "[+-][0-9a-zA-Z]+", or the value it denotes in the given `radix`
/// is not representable, the result is `nil`.
init?(_ text: String, radix: Int = default)

Ok, back to my usage problem example:

enum Error : ErrorType {
    case NotImplemented
}

struct Foo {
    init?(failable: Int) { return nil }
    init(throwable: Int) throws { throw Error.NotImplemented }
}

func example1() {
    guard let f = Foo(failable: 0) else {
        print("error")
        return
    }

    print("Foo(failable:) creation passed")
}

func example2() {
    do {
        let t = try Foo(throwable: 0)
        print("Foo(throwable:) creation passed")
    }
    catch {
        print("error: \(error)")
    }
}

example1()
example2()

The throws version really sucks in comparison. It's especially egregious because I'm going to be handling the failure case in exactly the same way.

Ok, so let's make it better:

func trythrow<T>(@autoclosure fn: () throws -> T?) -> T? {
    do { return try fn() }
    catch { return nil }
}

func example3() {
    guard let tt = trythrow(try Foo(throwable: 0)) else {
        print("error")
        return
    }

    print("Foo(throwable:) creation passed")

}

example3()

This gets us into a happy place again. We can turn failures where we do not care about the specific reasons into a code path that is more natural. If this becomes a pattern, it begs the question why this wouldn't be just built into the language.

Enter try?.

See this thread for some more great discussion.

// this won't compile, hypothetical syntax
func example4() {
    guard let tt = try? Foo(throwable: 0) else {
        print("error")
        return
    }

    print("Foo(throwable:) creation passed")
}

example3()

I think this is a big win. Note that while this appears to be encouraging one to ignore errors, it in fact doesn't. Many types we only care that a failure happened. This syntax simply reduces the complexity of the code when that is the case.

Also, try? is very searchable so it is trivial to audit your code for all of the uses of it to ensure error handling is indeed not needed in a fuller form.

Finally, because an Optional<T> is being returned, you are still forced into unwrapping it in order to use it.

I like it. Until we get it, trythrow() is going to be my goto.

P.S. Someone already logged rdar://21692462 on it. Great!

init? vs init throws