114

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
  • 33,269
  • 19
  • 164
  • 293
George WS
  • 3,903
  • 5
  • 27
  • 39

4 Answers4

132

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
  • 33,281
  • 23
  • 160
  • 191
rintaro
  • 51,423
  • 14
  • 131
  • 139
67

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
  • 33,165
  • 3
  • 43
  • 43
  • 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
17

In Swift 4 you can use Optional 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
}
YanSte
  • 10,661
  • 3
  • 57
  • 53
7

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
  • 33,281
  • 23
  • 160
  • 191
AJR
  • 91
  • 1
  • 5