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'"
guard vs control-flow-aware typing?
 
 
Q