33

We already know multiple optional bindings can be used in a single if/guard statement by separating them with commas, but not with && e.g.

// Works as expected
if let a = someOpt, b = someOtherOpt {
}
// Crashes
if let a = someOpt && b = someOtherOpt {
}

Playing around with playgrounds, the comma-style format also seems to work for boolean conditions though I can't find this mentioned anywhere. e.g.

if 1 == 1, 2 == 2 {
}
// Seems to be the same as
if 1 == 1 && 2 == 2 {
}

Is this an accepted method for evaluating multiple boolean conditions, and is the behaviour of , identical to that of && or are they technically different?

Cailean Wilkinson
  • 1,420
  • 2
  • 19
  • 32

7 Answers7

44

Actually the result is not the same. Say that you have 2 statements in an if and && between them. If in the first one you create a let using optional binding, you won't be able to see it in the second statement. Instead, using a comma, you will.

Comma example:

if let cell = tableView.cellForRow(at: IndexPath(row: n, section: 0)), cell.isSelected {
    //Everything ok
}

&& Example:

if let cell = tableView.cellForRow(at: IndexPath(row: n, section: 0)) && cell.isSelected {
    //ERROR: Use of unresolved identifier 'cell'              
}

Hope this helps.

mrc
  • 511
  • 5
  • 9
  • 12
    You are correct but the answer already states the behaviour you described and the question is asking about evaluating boolean conditionals specifically. – Josh Paradroid Feb 01 '18 at 12:45
  • 14
    Yes but in the other answer people is saying the results are the **same** and they're not. This is dangerous for people coming here and reading the answer, that's why I specified. – mrc Feb 01 '18 at 14:35
13

In Swift 3, the where keyword in condition clauses were replaced by a comma instead.

So a statement like if 1 == 1, 2 == 2 {} is saying "if 1 equals 1 where 2 equals 2..."

It's probably easiest to read a conditional statement with an && instead of a ,, but the results are the same.

You can read more about the details of the change in Swift 3 in the Swift Evolution proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0099-conditionclauses.md

nathangitter
  • 9,607
  • 3
  • 33
  • 42
  • 1
    So `1==1` is evaluated first? And if `1==1` (say) was to be `false`, would `2==2` still be evaluated, or would it be skipped as `&&` does? – Cailean Wilkinson Jul 08 '17 at 18:16
  • 1
    `2 == 2` is skipped, just like `&&` does – nathangitter Jul 08 '17 at 18:19
  • conditions are evaluated in direct order, even though one can think that `if 1 equals 1 where 2 equals 2...` from the answer implies that the part after `where` is evaluated first. It's not. The order is direct: `1==1`, then `2==2`. Also, read [mrc's answer](https://stackoverflow.com/a/48523772/1364257) about `,` vs `&&` difference in the `if let` statements. – voiger Dec 13 '18 at 10:45
4

When it comes to evaluating boolean comma-separated conditions, the easies way to think of a comma is a pair or brackets. So, if you have

if true, false || true {}

It gets transformed into

if true && (false || true) {}
bitemybyte
  • 971
  • 1
  • 10
  • 24
3

https://docs.swift.org/swift-book/ReferenceManual/Statements.html#grammar_condition-list

The Swift grammar says that the if statement condition-list can be composed by multiple condition separated by commas ,

A simple condition can be a boolean expression, a optional-binding-condition or other things:

enter image description here

So, using the comma to separate multiple expressions is perfectly fine.

Paull
  • 1,020
  • 1
  • 12
  • 21
2

Here is a case where they are sufficiently different as to require the ,. The following code will yield

'self' captured by a closure before all members were initialized

class User: NSObject, NSCoding {

    public var profiles: [Profile] = []    
    private var selectedProfileIndex : Int = 0

    public required init?(coder aDecoder: NSCoder) {
        // self.profiles initialized here

        let selectedIndex : Int = 100
        if 0 <= selectedIndex && selectedIndex < self.profiles.count {  <--- error here
            self.selectedProfileIndex = selectedIndex
        }

        super.init()
    }

...
}

This is due to the definition of && on Bool:

static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool

The selectedIndex < self.profiles.count on the RHS is caught in a closure.

Changing the && to , will get rid of the error. Unfortunately, I'm not sure how , is defined, but I thought that this was interesting.

hsuntn
  • 21
  • 2
0

When pattern matching a associated value in a switch, it matters when using a , or &&. One compiles, the other won't (Swift 5.1). This compiles (&&):

enum SomeEnum {
    case integer(Int)
}

func process(someEnum: SomeEnum) {
    switch someEnum {
    // Compiles
    case .integer(let integer) where integer > 10 && integer < 10:
        print("hi")
    default:
        fatalError()
    }
}

This won't (,):

enum SomeEnum {
    case integer(Int)
}

func process(someEnum: SomeEnum) {
    switch someEnum {
    // Compiles
    case .integer(let integer) where integer > 10, integer < 10:
        print("hi")
    default:
        fatalError()
    }
}
J. Doe
  • 12,159
  • 9
  • 60
  • 114
0

If you use comma, the compiler treats the whole thing like nested if's:

if conditionA, conditionB {

is treated like

if conditionA {
    if conditionB {

That's why

if let a = b, a > 10 {

works but

if let a = b && a > 10 {

does not! As the first one is in fact equivalent to

if b != nil {
    let a = b!
    if a > 10 {

yet in the second case, everything happens within the same scope and in that scope a was not defined, that's why you get

main.swift:3:17: error: cannot find 'a' in scope
Mecki
  • 125,244
  • 33
  • 244
  • 253