Background
I'm very fond of the concept of early exits, breaking execution as early as possible to make sure the correct conditions are met before getting to the punchline. Swift encourages this concept by use of the guard statement and needless to say I use it abundantly. However, I find that most of the time I end the statement with else { return } or else { return nil }. I write this so often it feels like boiler plate that could be inferred.
Pitch
If a function does not return a value or if it returns an optional, infer the else closure if omitted.
I think it would be easier to read and a lot nicer to work with. Should you want to use throw, fatalError() or any other way to break execution then the current syntax would still be available.
That could definitely save some keystrokes, and for void functions, it looks straightforward.
But imho including Optionals blurs the picture: nil is a quite obvious choice, but wouldn't it also be natural if
would return an empty array?
Afair, there are some languages whose handling of return value resembles inout variables, and with such a model, things like
I'll admit that void functions are a much better fit for inferred else closures and that Optionals might be too much magic for anyone learning the language but I still think it could apply for them. As for the [Int] example I do not propose any change to the current modus, but should all else closures be inferred I agree that the logical return value would be an empty [Int]. Again, I do not propose this.
I've always been a big skeptic of guard; it took me a while to really grok it. And I've also thought about whether something like a shortcut guard like this would be a good fit and my thoughts have usually come down to guard condition is not much better than writing the rest out.
That's an interesting idea, but like you say, I'm not so sure how good a fit that is for Swift. And IMO there's some other ergonomics with regards to functions that should probably be tackled first before this. Things like explicit partial-application syntax and the like.
I agree that it's not much better but I do think it's better. I'm not proposing we make drastic changes to the language but to simply add the option to write clearer code by omitting boilerplate.
What if the function throws? Do you want to set yourself up to silently swallow violated guards even though you wanted to throw an error?
I appreciate the explicitness here; it forces me to specifically say "ignore this".
Also, in our production code we usually at least debug-log something when a guard (checking a precondition) fails. In asynchronous code we call the response handler (with an error value). Or we throw. That is to say, I'm not sure I've had guard ... else { return } very often, if at all.
I like the clarity of explicit return and return nil. I'm not sure what's gained here and this covers only two cases: F(...) -> Void and F(...) -> T?.
An implementation of your pitch would be easier by changing guard to another word like guardEarlyReturn so it doesn't mess up parsing. But that makes it ugly.
I exclude guard!, which suggests fatalError and overlaps with assert/precondition and guard? which suggests only optional/nil returns.
@Jon_Shier I'm aware, the relevant part of my example is the optional closure.
@reitzig You can swallow unexpected errors with the current way of guard statement as well, albeit more explicitly so. You can still log using the closure but you don't always want to do so. I'm aware that Swift is not The iOS Language™ so forgive my for using a UIKit example but for instance the UIView function layoutIfNeeded() might not want to log anything should the layout not be needed.
@Erica_Sadun I'm all for clarity, I just like the idea of the else clause being optional as it often does nothing but returning.
Despite the list @benrimmington provided this idea does not seem to have made it to the list of common pitches I read prior to creating this thread. I take it this pitch is a popular opinion among quite a few of us.
I was just been thinking about the removal of the return statement last night so did a little digging and came across this (and several other) topics.
The idea behind the removal of the guard scope entirely (as proposed above) is an interesting one, I quite like it personally. It acts kind of like a conditional return statement. But at the same time I can see where @Chris_Lattner3 is coming from in it possibly not being totally clear. But I think that it's not totally clear because it's not a common thing to do. If we think about it as a 'conditional return' like I mentioned earlier, then it becomes a lot more clear. But one could then argue that if it's going to act like a conditional return then it should possibly be named something entirely different like escape (I'm terrible at naming things, but hopefully you can see my point).
What originally brought me here was the thought of not needing the return within the scope of the guard if the containing method didn't contain a return type. For example:
With the proposed solution you would get the compiler warning 'guard' body must not fall through, consider using a 'return' or 'throw' to exit the scope. We know a guard can't fall through, we know that the method doesn't throw or have a return type, so whats stopping the compiler from understanding that and implicitly returning instead of us having to explicitly state that it should return.
This is very much a small use case, but I feel like it's just one of those small rough edges in the language that could be smoothed out.
I hadn't considered that particular use case, but saying that I think it only makes sense to have an implicit return if it can be reasonably evaluated that one isn't needed. In your example there are several different options (break or return) so the compiler would require an explicit statement to understand what to do.
As a side note; I'm sure some people would look at the example not and go "don't use loops, did you not see the crusty talk at WWDC!"
It's not boilerplate and omitting it is not clearer.
There are several things you can do in the else block of a guard statement e.g. break, fatalError, throw. return is just one options and it is not clear to me why anybody would think it demands special treatment. For example in
for i in someSequece
{
guard i < 5 else { ... }
// do something
}
the most popular option is probably break.
It's clearer to be explicit about your intentions.
I wonder if this topic has been revisited a sufficient number of times that it might be worth considering as an addition to the list of commonly rejected changes.