Search code examples
swiftswitch-statementshort-circuiting

Do Swift Switch short circuit?


Do switch statements with more than one evaluations short circuit?

It probably doesn't matter, but I am curious.


Here's a simple example:

let one = 1
let two = true
let three = false


switch (one, two, three) {
case let (0, b, c) where b==c:
    break

case (0, true, true):
    break

default:
    break
}

In the first case statement, will the 'where' evaluation even happen?

In the second case statement, will the 'two' == true happen?


Solution

  • @J.beenie s answer treats your first question nicely (and convincingly). b == c will not be called since your initial one does not match 0, a classical AND short circuit.

    Your second question depends on the implementation of == for tuples. According to a comment to this question this has been part of the Swift-language since 2.2.1 and the standard implementation would of course short circuit since it is the fastest thing to do. So in your second case the second element would not be compared.

    Btw: You do not need to break in a Swift switch statement, instead you would fallthrough if you desire otherwise.

    Correction: My guess proved to be only half correct. Pattern matching in switch statements seems to do more than I expected. I tried to hijack == using my own Bool enum (roughly following this post (and adjusting for Swift 3)) and got some surprising results:

    import Cocoa
    
    let one = 1
    let two:MyBool = .myTrue
    let three:MyBool = .myFalse
    typealias ThreeTuple = (o:Int, tw:MyBool, th:MyBool)
    let tuple:ThreeTuple
    tuple  = (one, two, three)
    
    switch tuple {
    case let (1, b, c) where b == c:
       print("first case")    
    case (1, .myTrue, .myFalse):
        print("second case")
     default:
        print("default")
    }
    
    enum MyBool : ExpressibleByBooleanLiteral, Equatable  {
        case myTrue, myFalse
        public init() { self = .myFalse }
        public init(booleanLiteral value: BooleanLiteralType) {
            self=value ? .myTrue : .myFalse
        }
    }
    
    func ==(lhs: MyBool, rhs: MyBool) -> Bool {
        print("evaluate ==")
        switch (lhs, rhs) {
        case (.myTrue,.myTrue), (.myFalse,.myFalse):
            return true
        default:
            return false
        }
    }
    

    which yields

    evaluate ==
    second case
    

    at which point I was majorly surprised. The sole evaluation of == for MyBool values originates from the where b == c clause in the first case and all the tuple "comparisons" do not use the MyBool == function at all!! I suspected the optimiser was interfering so I turned the switch into a func as

    func match(_ tuple:ThreeTuple) {
        switch tuple {
         case let (1, b, c) where b == c:
            print("first case")
         case (1, .myTrue, .myFalse):
            print("second case")
         default:
            print("default")
        }
    }
    

    which should preclude excessive optimisation at compile time, but when I now ask for

    match((1, .myTrue, .myTrue))
    match((1, .myTrue, .myFalse))
    match((0, .myTrue, .myFalse))
    

    I get

    evaluate ==
    first case
    evaluate ==
    second case
    default
    

    where the evaluate == still originate only from the first case. So the only reasonable conclusion seems to be that there is "some other magic" going on during pattern matching in switch statements. I tried to google if I could figure out what that was, but to no avail so far. In any way, there seems to be way more short circuiting than I would have expected.