11
votes

In Swift, you can use if let optional binding to unwrap an optional into a constant or variable with the same name:

func test()
{
  let a: Int? = 1

  if let a = a {
    print("a = \(a)")
  } 
}

For everything inside the if let statement, the optional a is unwrapped into a regular int.

Likewise, I can use a guard statement to achieve a similar effect

func test()
{
  let a: Int? = 1

  guard let requiredA = a else{
    return
  }
  print("a = \(requiredA)")
}

However, I can't use code like this: guard let a = a else:

func test()
{
  let a: Int? = 1

  guard let a = a else{
    return
  }
  print("a = \(a)")
}

Why not?

In a guard statement, if the conditional of the guard statement fails, the else clause is executed and you exit the current scope. If the conditional succeeds, a new variable/constant is created from guard statement's closing brace to the end of the current scope.

Why can't I do the same trick of mapping an optional into a variable/constant with the same name for remainder of the current scope?

P.S.: I realize this question isn't a perfect fit for this site. I'm open to suggestions as to where would be a better place for this question.

1
Looks to me like the if way creates two variables in different scopes, which is fine, while the guard way would be creating two identically-named variables in the same scope, which would be a weird special case and complicate the variable resolution rules to allow it. I'm guessing, though; I don't know Swift.user2357112 supports Monica

1 Answers

15
votes

The reason you can't do this:

func test()
{
  let a: Int? = 1

  guard let a = a else{
    return
  }
  print("a = \(a)")
}

is because guard creates the new variable in the same scope, thus you have two variables called a in the same scope. One is an Int and the other is an Int?. That is not allowed.

The error that you get Definition conflicts with previous value is exactly the same as if you had done this:

func test()
{
    let a: Int? = 1

    let a = a!
}

Compare that with:

func test()
{
    let a: Int? = 1

    if let a = a {
        print("a = \(a)")
    }
}

In this case, the new variable a which is an Int exists only in the new scope of the if's then clause, so this works.


From the comments:

But I submit to you that the section of code after the closing brace and to the end of the enclosing scope is actually an inner scope.

I can understand that you would like it to be so, but it isn't. If that were the case, then you could do this, but it too gives an error:

func test()
{
    let a: Int? = 1

    guard let b = a else{
        return
    }
    print("b = \(b)")

    let a = 5  // Definition conflicts with previous value

    print("a = \(a)")
}

The beauty of guard is that it doesn't create new scopes and you avoid creating the pyramid of death that results when you repeatedly use if let to unwrap optionals (and in the process create new scopes).


See the follow-up question When did guard let foo = foo become legal? for more insight on this topic.