1

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

zardon
  • 1,601
  • 4
  • 23
  • 46

2 Answers2

2

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)")
}
dfrib
  • 70,367
  • 12
  • 127
  • 192
1

Just use optional casting instead of force casting. Using force casting result would have a non-optional value even if used without the if let statement.

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

}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • This will perform an attempted conversoin from `(Bool, Error?)` to `(Bool, Error?)`, i.e., an attempted conversion from a type to itself, which will always succeed (and if you try this out in your project, the compiler should warn you that this case always succeeds). So this is really just a roundabout way to assign an instance to a property (`result`) by first packing it into the `.some(...)` of an optional, then immediately unpacking it back to a concrete value. – dfrib Aug 04 '17 at 13:47