swift: if-let with parallel assignment

9.2k Views Asked by At

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?

2

There are 2 best solutions below

5
Sergey Kalinichenko On BEST ANSWER

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")
}
0
Chris Conover On

This is probably evident by now, but your quest for this expected functionality can end with the release of Swift 1.2.

From the swift blog: https://developer.apple.com/swift/blog/?id=22

More powerful optional unwrapping with if let — The if let construct can now unwrap multiple optionals at once, as well as include intervening boolean conditions. This lets you express conditional control flow without unnecessary nesting.

The short of it though, is that you can now do what you are asking, in addition to constraining the values with where clauses.

if let a = foo(), b = bar() {
}