Search code examples
swifttuplesoption-typedo-catch

Swift How to returning a tuple from a do catch where conditional binding must have optional type?


I am wanting to put a swift 3 do-catch inside a function rather than constantly writing it everywhere I need it; inside this function I wish to return a tuple with a boolean, and an optional error.

I am trying to return a tuple from the function and handle the result in my XCTest

However, I get an error saying:

Initializer for conditional binding must have Optional type, not '(Bool, Error?)' (aka '(Bool, Optional)')

My function is as follows;

public static func isValidPurchase(train: Train, player: Player) -> (Bool, Error?) {
    do {
        let result = try train.canBePurchased(by: player)
        return (result, nil)
    } catch let error {
        return (false, error)
    }
}

My canBePurchased code is a bit long, but it goes like this:

func canBePurchased(by player: Player) throws -> Bool {

        if (!self.isUnlocked) {
            throw ErrorCode.trainIsNotUnlocked(train: self)
        }

    // other if-statements and throws go here
}

And in my XCTest I call it as such:

if let result = TrainAPI.isValidPurchase(train: firstTrain, player: firstPlayer) as! (Bool, Error?) {

}

I've tried to force cast:

if let result: (Bool, Error?) ...

but this only demotes the compiler error to a warning.

The complier displays the error as noted above.

What am I doing wrong in terms of Initializer for conditional binding must have Optional type and how do I avoid it?

Thanks


Solution

  • The return type from isValidPurchase(train:player) is (Bool, Error?), which is not an optional (it is a tuple where the 2nd member happens to be an optional). Hence, there is no use for optional binding when capturing the return from a call to isValidPurchase(train:player). You simply assign the return value and study it's content (possible error etc) from there:

    // e.g. using explicitly separate tuple members
    let (result, error) = TrainAPI
        .isValidPurchase(train: firstTrain, player: firstPlayer)
    
    if let error = error { /* you have an error */ }
    else { /* no error, proceed with 'result' */ }
    

    Or, studying the return using a switch statement:

    // result is a tuple of type (Bool, Error?)
    let result = TrainAPI
            .isValidPurchase(train: firstTrain, player: firstPlayer)
    
    switch result {
        case (_, let error?): print("An error occured!")
        case (let result, _): print("Result = \(result)")
    }