Search code examples
swiftguard

When did `guard let foo = foo` become legal?


Back in November of 2016 I posted a question asking why I couldn't use guard to create an unwrapped version of a variable using the same name as the optional, like you can with if let:

Link: Why isn't guard let foo = foo valid?

When I wrote that question, the code below would fail to compile with an error that "Definition conflicts with previous value":

//Test of using guard to create an unwrapped version of a var, like if let
func guardTest(_ viewController: UIViewController?) -> UIViewController? {
  // Check if the current viewController exists
  print(String(describing: viewController))
  guard let viewController = viewController else {
    return nil
  }
  print(String(describing: viewController))

  return viewController
}

However, I just found some code at work that does this, and it now compiles without complaint and does what I want it to do! When run, the print statements show that foo is an optional before the guard, and an unwrapped optional after:

viewController = Optional(<TrochoidDemo.ViewController: 0x7ff16a039a00>)
viewController = <TrochoidDemo.ViewController: 0x7ff16a039a00>

(I added the test function guardTest(_:) to my latest open source project if you want to try it out. It's available on Github at https://github.com/DuncanMC/TrochoidDemo)

I'm happy that this construct now works as I want it to, but confused as to why it's now legal, and when the change occurred.

Is anybody aware of a recent change to the language definition that makes this construct work where it didn't before?


Solution

  • TL;DR

    guard let foo = foo is legal if foo was defined in another scope.


    The example from your linked question:

    func test()
    {
      let a: Int? = 1
    
      guard let a = a else{
        return
      }
      print("a = \(a)")
    }
    

    still doesn't work because the guard statement is trying to create another variable a in the same scope.

    This example:

    //Test of using guard to create an unwrapped version of a var, like if let
    func guardTest(_ viewController: UIViewController?) -> UIViewController? {
      // Check if the current viewController exists
      print(String(describing: viewController))
      guard let viewController = viewController else {
        return nil
      }
      print(String(describing: viewController))
    
      return viewController
    }
    

    works for the same reason that this does:

    func test(a: Int)
    {
        print(type(of: a))  // Int
    
        let a = 3.14
    
        print(type(of: a))  // Double
    }
    

    The parameter to the function is defined in a different scope, so Swift allows you to create a local variable with the same name.