guard vs control-flow-aware typing?

I like the new guard syntax, but it surprised me. I was expecting some control-flow aware typing (or whatever it's called). I'm curious... do you think these are just two ways to do the same thing, or does the guard syntax offer some advantage?


// foo: Foo?
guard let foo = foo else { throw Error.NoFoo }
// Now, foo: Foo
foo.doStuff()

vs...

// foo: Foo?
if foo == nil { throw Error.NoFoo }
// Now, foo: Foo
foo.doStuff()


Rob

import Darwin // (For clock())
struct Foo { func doStuff() { print("Fooing something ...") } }
func maybeFoo() -> Foo? { return clock() & 1 == 0 ? Foo() : nil }

func earlyExitUsingGuard() {
    guard let foo = maybeFoo() else { print("No foo for you!"); return } // <-- Note that you'll get an error if you don't throw or return here.
    foo.doStuff() // <-- Note that it doesn't have to be force-unwrapped, since the compiler can know that foo is not .None.
}

func earlyExitUsingIf() {
    let foo = maybeFoo()
    if foo == nil { print("No foo for you!"); return }
    foo!.doStuff() // <-- Note that it needs to be force-unwrapped.
}

let fn = earlyExitUsingGuard
for _ in 0 ..< 10 { fn() }


This is explained in the documentation.

As Jens said, the difference is that 'guard let' established a new meaning for the symbol to the end of the scope. There has been some discussion in the old forums of having the compiler track nil-ness in the code so that it can eliminate unnecessary unwrapping without a special construct, but it doesn't seen to have gained much traction (yet).


Having just spent a couple of days converting code and using 'guard <expr>' and 'guard let' a lot, I find I like it a lot more than I expected to. It documents that the statement is about handling exceptional cases (though of course it doesn't have to be), and this lets the eye "slip over" guard statements when you're reviewing a method body, and see the more mainline bits.

Agreed. I like that it's self-documenting like that.


The more I think about it, the more the guard syntax makes sense. With my hypothetical syntax, with the plain `if`, it could get complicated figuring out how the test expression might effect the types within the if block.


Rob

You wrote:


guard let foo = foo else { throw Error.NoFoo }


which is not actually valid, since you redefine foo. If the compiler reacted in the same way to:


if foo == nil { throw Error.NoFoo }


it would have to redefine foo. I imagine that they considered redefining a variable in a scope to be an invalid action, just as you cannot redefine foo with a let or a var without starting a new scope. I think it's reasonable to expect the same rules to apply to the language statements.


The options I can think of are:

  1. Allow if statements to redefine variables in the same scope, and still disallow the programmer to redefine variables in the same scope, or
  2. Allow if statements to redefine variables in the same scope, and also allow the programmer to redefine variables in the same scope, or
  3. Disallow everyone from redefining variables in the same scope.


I think 1 is bad because of inconsistency, and I think 2 is bad because it would lead to too many errors.

Actually, the discussion about this in the past had many proponents of the redefinition approach. (Well, specifically, shadowing variables without a warning.)


I haven't studied it in detail, but AFAICT in Swift 2, you can 'if let x = x' to shadow a property name, but you get a warning if you try to shadow a local variable name. When you're used to Obj-C, 'if let x = x' seems like insanity, but after you get used to it as a pattern (of removing optionaiity), it's quite good IMO.


In those circumstances, it wouldn't be ridiculous for the compiler to generate the shadowing automatically. It's just not obvious yet what the pitfalls might be if the programmer isn't expecting it.

But the problem is that it isn't shadowing. Shadowing is permitted, but redefining is not. If there are no curly braces, it is the same scope. When using an if let, shadowing is used, so it doesn't break this rule.

It is in effect the same thing. In particular, you can currently write this code (where "x" is a property of optional type):


guard let x = x else {return}
// 'x' is an unwrapped local for the rest of the scope


Note that it's not really unsafe, because you can't assign to the new 'x', and it has the same value as the old x.


All that's really in question here is whether this sort of thing is always going to require an explict "let x = x" somewhere in the syntax, as above, or whether this in the future might be done silently by the compiler.

No:


Welcome to Apple Swift version 2.0 (700.0.38.1 700.0.53). Type :help for assistance.
  1> func foo() {
  2.     let x : String?
  3.     
  4.     guard let x = x else { return }
  5. }
repl.swift:4:15: error: definition conflicts with previous value
    guard let x = x else { return }
              ^
repl.swift:2:9: note: previous definition of 'x' is here
    let x : String?
        ^

Correct, as I said earlier in the thread, in Swift 2, there's an error if you try to shadow a local variable, but it works fine to shadow a property.


Again, as I said earlier in the thread, there was discussion in the old forums, in which some people wanted unlimited shadowing, and other people didn't want any shadowing. This was in Swift 1.2 days. At the time, IIRC, Chris Lattner said he wasn't sure what the right balance was, but it looks like Swift 2 has taken an intermediate path.


I'm not saying you're wrong to dislike shadowing, just that there are other opinions out there, and Swift 2 has it already, in some scenarios.

It's annoying that in `if` we can shadow the variable (because of a new scope) but with `guard` we cannot. At least in simple cases where we just check for non-nil and thus won't need the previous Optional variable anymore.


I think we can expect to see a lot ugly-named variables in the future e.g. for an optional `x`: `theX`, `validX`, `realX`, `existingX`, `nonNilX` or something like that.


An additional shorter version of `guard` could solve this.

You keep saying "shadowing". Shadowing is allowed, but redefining is not. The code I showed you is redefining x, not shadowing it. If you try to shadow a local variable it works fine.


One more time:

  • Shadowing is when you define a variable in a scope when the same name is already used in the outer scope. The variable in the outer scope cannot be referred to, which is why it is called "shadowing".
  • Redefining is when you try to define a variable with a name that is already used in the same scope.


If you want to have a language that creates a new scope for every line, then that is fine, but Swift is currently not that language.

Yes, you're 100% correct concerning the terminology, and (as fluidsonic pointed out and I had missed) you may also be 100% correct that compiler is allowing shadowing and not redefinition.


However, I regret to inform you that what I really care about is what fluidsonic said, namely that 'guard let x = x' should be valid in the same situations where 'if let x = x' is valid. If that means that 'guard let' causes redefinition, so be it. If that means that 'guard' causes the rest of the original scope to be silently enclosed in an invisible scope so that it's shadowing instead of redefining, so be it. Etc.


The whole point of the-language-feature-we-wanted-that-turned-out-to-be-guard-let is that it does what if-let does but (a) reverses the test, and (b) doesn't introduce any more indentation or nesting into the source. Otherwise, it's merely decorative.

Maybe I'm misunderstanding your concern, but if you're only doing a nil-check, you can already do that without creating a new variable using _:


func testIt(string: String?) {
   
    guard let _ = string else { return }
   
    print("you said '\(string!)'")
}

The idea is that once you get past the guardian, there should be no more need to unwrap that optional, because you know you don't need to. The explicit unwrapping you suggest is possible, but:


— There's a hidden test every time you execute that statement


— It makes reading the source code more difficult. In your example: ! ) ' " ). And that was a simple example!


— It exposes the method to more thread safety issues (when the guarded variable is an instance or global variable) that a one-time unwrapping might avoid.


— It promotes the idea of randomly throwing in a "!" to make compilation errors goes away. IOW, it's a code smell.

OK. Well, I modified the code to capture both property and local variable, and both seem to work in a playground... I appologise, I guess I'm just not following well enough to understand the issue at play. I'll bow out and watch from the sidelines.


struct t {
    var intro: String?
  
    func testIt(string: String?) -> String {
      
        guard let string = string else { return "" }
        guard let intro = intro else { return "" }
      
        return "\(intro) '\(string)'"
    }
}

var test = t()
test.testIt(nil)      /// -> ""
test.intro = "You said"
test.testIt(nil)      /// -> ""
test.testIt("foo")    /// -> "You said 'foo'"

FWIW, with this:


struct a {
  let w: Int? = 1
  func b (z: Int?) {
  let x: Int? = 5
  guard let w = w else { return } // #1
  guard let x = x else { return } // #2
  let y = x
  guard let y = y else { return } // #3
  guard let z = z else { return } // #4
  print ("\(w)\(x)\(y)\(z)")
  do {
  let x = 1 // #5
  print ("\(x)")
  }
  }
}


I get compiler errors on #2 and #3. All of the others are accepted.

Since we now agree on the terminology, let me ask once again. Which option do you prefer:


  1. Allow if statements to create an implicit scope after their block is done, and still disallow normal let statements create implicit scopes, or
  2. Allow if statements to create an implicit scope after their block is done, and also allow normal let statements create implicit scopes, or
  3. Disallow everyone from creating implicit scopes.


Clearly you don't prefer 3, because that is what you are arguing against. Would you choose 2, and thereby allowing code like this:

let x : String?
let x = x!


In that case, why? And if not, why?


If you allow the implicit scopes, are other variables allowed to be shadowed from what looks like the same scope?


And perhaps most importantly, how do you communicate these rules to the programmers?


These are questions that are easy to ignore when you are suggesting changes to a language, but if you are designing one you have to think of the consequences. If you have a serious suggestion for a change to a language, of course you don't have to think of the consquences, you can make the suggestion anyway, but if you do, the suggestion is more likely to be good.

I'm not sure I understand the question completely. There are 4 different statements we could be talking about here:


1. if … { … }

2. if let x = … { … }

3. guard … else { … }

4. guard let x = … else { … }


In all 4 cases, there's a new scope inside the braces, which follows the compiler rules for new scopes inside braces. That's not what we're talking about.


In cases 1 and 3, there's no other scoping consideration, and so those cases are not what we're talking about either.


In cases 2 and 4, there's a scope-related side effect, but it's — intentionally — asymmetric. For 'if let', a new uwrapped x is defined inside the braces. For 'guard let', a new uwrapped x is defined for the rest of the current scope, and there's no new definition of x inside the braces. (I checked: if you try to refer to x inside the braces, you get a warning that it's not the x you think it is.)


Case 2 behaves as expected now, because it's always "just" shadowing, as we normally understand it, so it doesn't cause an error when x is a local variable.


Case 3 is weird, because one of three different things can happen (according to the tests I posted earlier):


a. If the original x is a property, the guard's new definition of x "shadows" the property in the rest of the scope


b. If x is a parameter, the new x "redefines" the parameter in the same scope, without an error (surprisingly)


c. If x is a local variable, the new x "redefines" the local variable, and thus produces the error


What I want is for 'guard let' not to produce a redefinition error, ever, so that it's usable in all the same cases as 'if let'. If I've done my accounting properly, that means everything is working reasonably (by my standards), except that case 3c should not produce an error.

"There has been some discussion in the old forums of having the compiler track nil-ness in the code so that it can eliminate unnecessary unwrapping without a special construct."


That'd probably be the nicest solution. I've only done a wee-bit of Swift. In ObjC..you usually don't have messages to nil propagate too deep...but thinking about peppering my code with all these

? ! if let guard...


Kind of makes me cringe a bit. Is it at all possible that this being over-thought?

As far as I can see, "guard let" works in the same places as "let" does. The "let" below is not redefining the argument y, it is shadowing it, because y is from an outer scope (outside the braces).


func foofunc(y: String?) {
    let y = 1
    print(y)
}


Think of "guard let x = y" as the same thing as "let x = y!" but with explicit error handling instead of a crash.

So, my question still stands, would you allow this code, i.e. should a let statement be able to create an implicit scope:

let x : String?
let x = x!
func foofunc(y: String?) {
    let y = 1
    print(y)
}


I would call the above a bug. Is there a rationale for allowing it?


>> would you allow this code,


let x : String?
let x = x!


No, I don't see any value in allowing that, at least not for the kinds of reasons that inspired 'if let' (which I regard as a different statement, with its own syntax and semantics).


To make that explict: I think 'if let' was invented to "declare" a section of source code where unwrapping had already happened. The fuss about adding 'guard let' is to get the same effect without needing a new explicit scope and its accompanying indentation. Therefore:


>> As far as I can see, "guard let" works in the same places as "let" does.


What I expect/wish is that 'guard let' works in the same places as 'if let' does.

I think I see your issue now. I definitely think you're wanting to use the guard statement in a way contrary to how it was intended... I think typically you'd use 'guard' at the very top of a function to early exit if the incoming parameters are other than expected. For use within the function, I think the idea is that you would use 'if let' like before. So to refactor your code above:


struct a {
    let w: Int? = 1
   
    func b (z: Int?) {
        guard let w = w else { return }
        guard let z = z else { return }

        let x: Int? = 5
        if let x = x {
            let y = x
            print ("\(w)\(x)\(y)\(z)")
            do {
                let x = 1 /
                print ("\(x)")
            }
        }
    }
}


I suppose it's not what you want, but I personally think this is fairly clear. The 'guard let' and 'if let' phrases each serve a distinct purpose: guards tell you that the function can exit early and under what conditions, 'if let' is a flow-control. Using a 'guard' deep within a function doesn't make a whole lot of sense, IMHO (tho I realize it's just an opinion).

Well, no, that's not the real issue.


The underlying issue is a programming pattern that doesn't have an official name (AFAIK), but that I call "mainline coding", where in any given method the normal path of execution is a series of unindented statements. Everything that is indented is an exceptional (error, optional, alternate) path of execution. This tends to make methods very easy to read, and somewhat more bug free, because you don't need to deduce the actual path of execution in two dimensions.


For example, here's how you would have to write some real code in Swift 1.0:


var error

if let data = NSData.readFromFile (…, &error) {

if let dictionary = NSJSONSerialization.JSONObjectWithData (data, 0, &error) as? NSDictionary {

return dictionary

}

else {

return somethingElse ()

}

}

else {

return somethingElse2 ()

}


Here's how it improved in Swift 2:


guard let data = try NSData.readFromFile (…) else { return somethingElse () }

guard let dictionary = try NSJSONSerialization.JSONObjectWithData (data, 0) as? NSDictionary else { return somethingElse2 () }


return dictionary


Suddenly it's obvious that this method is intended to return a dictionary. In the original, you have to hunt around for that information, and it's not even a complicated example.


Note that there's no presumption that this code is at the beginning of method. It might actually be towards the end. 'guard' is not limited to "early" exits, if that means exits at the top of a method.


P.S. I cheated a little bit with the comparative formatting, to make the difference more emphatic. In practice, the difference tends to be as dramatic as I've shown it here.

guard vs control-flow-aware typing?
 
 
Q