110

In Swift, how can I write a case in a switch statement that tests the value being switched against the contents of an optional, skipping over the case if the optional contains nil?

Here's how I imagine this might look:

let someValue = 5
let someOptional: Int? = nil

switch someValue {
case someOptional:
    // someOptional is non-nil, and someValue equals the unwrapped contents of someOptional
default:
    // either, someOptional is nil, or someOptional is non-nil but someValue does not equal the unwrapped contents of someOptional
}

If I just write it exactly like this, the compiler complains that someOptional is not unwrapped, but if I explicitly unwrap it by adding ! to the end, I of course get a runtime error any time someOptional contains nil. Adding ? instead of ! would make some sense to me (in the spirit of optional chaining, I suppose), but doesn't make the compiler error go away (i.e. doesn't actually unwrap the optional).

mfaani
  • 28,843
  • 15
  • 145
  • 252
George WS
  • 3,691
  • 5
  • 24
  • 39

4 Answers4

128

Optional is just a enum like this:

enum Optional<T> : Reflectable, NilLiteralConvertible {
    case none
    case some(T)

    // ...
}

So you can match them as usual "Associated Values" matching patterns:

let someValue = 5
let someOptional: Int? = nil

switch someOptional {
case .some(someValue):
    println("the value is \(someValue)")
case .some(let val):
    println("the value is \(val)")
default:
    println("nil")
}

If you want match from someValue, using guard expression:

switch someValue {
case let val where val == someOptional:
    println(someValue)
default:
    break
}

And for Swift > 2.0

switch someValue {
case let val where val == someOptional:
    print("matched")
default:
    print("didn't match; default")        
}
pkamb
  • 30,595
  • 22
  • 143
  • 179
rintaro
  • 50,337
  • 13
  • 128
  • 138
63

As of Xcode 7, “a new x? pattern can be used to pattern match against optionals as a synonym for .some(x)”. This means that in Swift 2 and later the following variation of rintaro's answer will work as well:

let knownValue = 5

switch someOptional {
case knownValue?:
    // Contents of someOptional are knownValue, defined above.
case let otherValue?:
    // Contents of someOptional are *any* non-nil value not already tested for.
    // Unwrapped contents are assigned to otherValue for use inside this case.
default:
    // someOptional is nil.
}
Slipp D. Thompson
  • 30,655
  • 3
  • 41
  • 42
  • 3
    The question is about matching a non-optional value against an optional, this answer is the other way around. – Martin R Feb 20 '17 at 14:31
  • 2
    True, however this answer was originally written by the OP as an update to the question so to him it was irrefutably a viable solution; I just moved it to a community wiki answer. Perhaps @GeorgeWS can clarify as to why switching the switch & case args works for his use-case? – Slipp D. Thompson Feb 20 '17 at 14:56
  • 2
    I'm a bit lost. what's the difference between your first two cases? `someValue?` is some other defined value, but `case let val?` is just the safe unwrapped version of `someOptional`?! – mfaani Nov 17 '17 at 12:51
  • 1
    @Honey It's not a real-world code example; it's simply a variation on rintaro's answer. So go ask him/her that question— my answer is functionally equivalent to the code in his/hers. If you were to ask rintaro though, I believe the answer would be 1. it mirrors what's in the linked Apple docs; 2. it only demonstrates the syntax; it does not accomplish a distinct calculation or business logic goal. – Slipp D. Thompson Feb 25 '18 at 11:25
  • @Honey Also, rintaro's answer was originally written for Swift 1.x and updated for Swift 2. It's possible that the version without `let` no longer compiles. I can't remember right now why that would've worked back in the day. – Slipp D. Thompson Feb 25 '18 at 11:29
  • @Honey I think you're exactly right about the first two cases—the difference is that `someValue` was presumed to be a specific, known value we were checking for, whereas `val` was just *any* other non-optional value that `someOptional` might contain. I updated the code sample to make this more clear (renamed `someValue` → `knownValue` and `val` → `otherValue`, showed `knownValue` being assigned before the `switch`, and added more precise comments). Thanks for pointing out the confusing-ness of this—*totally* see why it was confusing, looking back at it a year+ later! – George WS Sep 29 '19 at 19:49
  • @SlippD.Thompson The short answer to why this worked for me is… I don't remember. ‍♂️ Longer answer, looking back—I think switching the `switch` and `case`s worked here for me because I was able to find out what I wanted to know with either method of constructing the `switch` statement. Looking at the original question, I wanted to know if… `// someOptional is non-nil, and someValue equals the unwrapped contents of someOptional` …which is true both in the first case of my imagined-switch in the question, and in the analogous second case here (`case let otherValue?`). – George WS Sep 29 '19 at 20:00
14

In Swift 4 you can use Optional : ExpressibleByNilLiteral of Apple to wrappe optional

https://developer.apple.com/documentation/swift/optional

Example

enum MyEnum {
    case normal
    case cool
}

some

let myOptional: MyEnum? = MyEnum.normal

switch smyOptional {
    case .some(.normal): 
    // Found .normal enum
    break

    case .none: 
    break

    default:
    break
}

none

let myOptional: MyEnum? = nil

switch smyOptional {
    case .some(.normal): 
    break

    case .none: 
    // Found nil
    break

    default:
    break
}

default

let myOptional: MyEnum? = MyEnum.cool

switch smyOptional {
    case .some(.normal): 
    break

    case .none: 
    break

    default:
    // Found .Cool enum
    break
}

Enum with value

enum MyEnum {
    case normal(myValue: String)
    case cool
}

some value

let myOptional: MyEnum? = MyEnum.normal("BlaBla")

switch smyOptional {
case .some(.normal(let myValue)) where myValue == "BlaBla":
    // Here because where find in my myValue "BlaBla"
    break

// Example for get value
case .some(.normal(let myValue)):
    break

// Example for just know if is normal case enum
case .some(.normal):
    break

case .none:
    break

default:

    break
}
YannSteph
  • 10,204
  • 3
  • 53
  • 47
  • 1
    You mention `ExpressibleByNilLiteral` but then that is not actually used in the enum declarations etc. What is that? – pkamb Sep 22 '20 at 04:41
6

You can explicitly mention all cases along with nil as an additional case to handle the optional:

switch optionalEnumValue {
case .caseOne:
    break
case .caseTwo:
    break
case .caseN:
    break
case nil:
    break
}
pkamb
  • 30,595
  • 22
  • 143
  • 179
AJR
  • 71
  • 1
  • 5