When did `guard let foo = foo` become legal?
Asked Answered
M

1

11

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?

Materse answered 25/1, 2017 at 14:31 Comment(3)
This still doesn't work to redefine another local variable in the function. This seems to be related to the fact that Swift will allow you to create a local variable that has the same name as an input parameter to the function. I don't know if that always worked, but var a = a is now the way to convert an input parameter into a var since you can't put var in the function signature anymore.Creasy
Ok, why the down votes? If you think my question is poor, please explain why.Materse
I up voted of course. Any question that challenges something that I thought I understood well is a good question in my book. I kind of wish SO forced you to make a (constructive, hopefully) comment when down voting or at a minimum up voting someone else's constructive comment.Creasy
C
13

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.

Creasy answered 25/1, 2017 at 14:50 Comment(5)
Ok, that makes sense. Thanks for clearing it up. It seems strange that your example is legal, since your let a = 3.14 makes the parameter inaccessible.Materse
That happens all the time. Local variables hide other variables from enclosing scopes. The thing that puzzles me is why the first print(type(of: a)) doesn't give an error, but it does in this case let a = 3 func test() { print(type(of: a)) let a = 3.14 print(type(of: a)) }Creasy
I think of parameters to a function as being in the local scope, not an enclosing scope. This discussion makes it clear that parameters are actually considered to be from an enclosing scope. Thus var a=a is legal when a is a parameter, since it takes the constant from the enclosing scope and redefines it as a variable in the current scope.Materse
Scoping for function parameters and for variables created in a for loop are the same. Consider: for i in 1...3 { print(i); let i = 3.14; print(i) }Creasy
It looks to me like there is a rule in Swift that, just like C, braces define a new level of scope, including function braces and the statement braces around for..in statements. (Most of that is obvious. For functions, the parameters are declared outside the braces, so the parameters are considered an outer-level scope to the body of the function.)Materse

© 2022 - 2024 — McMap. All rights reserved.