swifttuplesswift-playgroundoptional-valuesparallel-assignment

swift: if-let with parallel assignment


I'm trying to make a simple if-let statement with more than one value. The if block should be executed only if all optional vars are non-nil, plus they should be assigned to the new let-vars (constants?) that live only inside the if block, just like the normal single-assignment if-let.

var a: String? = "A"
var b: String? // nil

if let (m, n) = (a, b) {
    println("m: \(m), n: \(n)")
} else {
    println("too bad")
}
// error: Bound value in a conditional binding must be of Optional type
// this of course is because the tuple itself is not an Optional
// let's try that to be sure that's the problem...

let mysteryTuple: (String?, String?)? = (a, b)
if let (m, n) = mysteryTuple {
    println("m: \(m), n: \(n)")
} else {
    println("too bad")
}
// yeah, no errors, but not the behavior I want (printed "m: A, n: nil")

// and in a different way:
if let m = a, n = b {
    println("m: \(m), n: \(n)")
} else {
    println("too bad")
}
// a couple syntax errors (even though 'let m = a, n = b'
// works on its own, outside the if statement)

Is this even possible? If not (which I'm guessing), do you think Apple will (or should) implement this in the future?


Solution

  • Before deciding whether it is possible or not, consider why if - let ... conditionals work with a single optional value: the reason this code compiles

    if let constVar = testVar {
        ...
    }
    

    is that all optional types conform to the LogicalValue protocol, which handles the null checking of the optional value.

    This explains why your trick with an optional tuple did not work either: the implementation of the LogicalValue checked if the tuple itself is non-null, ignoring its components. The logic behind Apple's decision is clear: rather than making an exception for tuples when all their element types are optional, they took the uniform approach, and treated the tuple in the same way that they treat other optional types.

    Of course, implementing the logic that you are trying to implement is easy with an additional line of code:

    if a != nil && b != nil {
        let (m, n) = (a!, b!)
        println("m: \(m), n: \(n)")
    } else {
        println("too bad")
    }