7

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?

maltalef
  • 1,507
  • 2
  • 16
  • 27
  • What Apple will or should do is not a subject for Stack Overflow. – matt Jul 18 '14 at 17:40
  • 2
    possible duplicate of [Using "if let..." with many expressions](http://stackoverflow.com/questions/24118900/using-if-let-with-many-expressions) – Martin R Jul 18 '14 at 18:09
  • 1
    As noted in the duplicate question's top answer, you can get this with a `switch` statement -- `if` is for binary tests, `switch` is for *n*-ary & multidimensional pattern matching. – rickster Jul 18 '14 at 18:25
  • You're right, I didn't see that question in my initial search, but that pretty much is it. – maltalef Jul 21 '14 at 19:26

2 Answers2

10

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() {
}
Chris Conover
  • 8,889
  • 5
  • 52
  • 68
9

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")
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • What is the purpose of the assignment `let (m, n) = (a, b)`? In contrast to `if let ...` it does not unwrap the optionals. So you could just test `if a && b ...`. – Martin R Jul 18 '14 at 18:13
  • @MartinR You are right, this is even better, because the scope of `m` and `n` remains confined to the `if` statement. Although the values are not visible in the `else` branch, it shouldn't be too much of an issue, because one of them is empty anyway, so the program would have to check them separately again in order to do anything useful with them. – Sergey Kalinichenko Jul 18 '14 at 18:20
  • Oddly, if you type `a` and `b` as `String!` instead of `String?`, that code crashes the compiler. For this and other reasons, the use of `if a` to test for nil should be avoided completely; instead, take advantage of the fact that nil is now a literal and compare _explicitly_: `if a != nil`. This may become even more important in the future, since there is a chance that implicit nil-testing will be removed from the language. – matt Jul 18 '14 at 18:38
  • Don't you want `let (m, n) = (a!, b!)` ? Just assigning without unwrapping will leave you with two new optional values. – vacawama Jul 18 '14 at 18:56
  • @matt You are right, thank you for the comment! I keep forgetting that nothing about Swift is cast in stone at the moment, and that some features may be removed later. – Sergey Kalinichenko Jul 18 '14 at 19:02