Inferred return for guard statement

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.

For example:

// Current
func voidFunction() {
    guard condition else { return }
    doTheThing()
}

func optionalFunction() -> SomeType? {
    guard let dependency = source else { return nil }
    return SomeType(with: dependency)
}

// Proposed inference
func voidFunction() {
    guard condition
    doTheThing()
}

func optionalFunction() -> SomeType? {
    guard let dependency = source
    return SomeType(with: dependency)
}

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.

5 Likes

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

func foo(arg: Int) -> [Int] {
   guard arg > 0
   // ....
}

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

func optionalFunction() -> SomeType? = nil {
    guard let dependency = source
    return SomeType(with: dependency)
}

feel quite natural.
I’m just not sure if this would be a good fit for Swift.

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.

This seems nice at first, but consider something like:

func someFunction(_ someArg: Int) -> ((Int, Int) -> Int) = { (a: Int, b: Int) in a * b } {
// ... 
}

It’s probably an edge-case but in my opinion it looks confusing, especially for beginners.

I would agree that for void functions an inferred return within guards would definitely make sense.

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.

1 Like

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.

Consider the following function:

func aFunction(with optionalClosure: (() -> Void)? = nil) {
    guard let closure = optionalClosure else { return }
    closure()
}

When calling it:

// The closure can be omitted...
aFunction()

// ...or used in case you need it.
aFunction {
    doNecessaryThings()
}

I’m aware that guard is a statement and not a function but I’d argue that optional closures is a familiar concept to Swift users.

Just FYI, you don’t need guard at all in you example, as you can call optional closures directly. e.g. optionalClosure?()

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.

1 Like

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.

4 Likes
2 Likes

Just MHO, but this feels like it favors terseness over clarity, which isn’t the way that Swift typically balances.

12 Likes

@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.

func layoutIfNeeded() {
    guard needsLayout
    // layout
}

@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:

// Currently
func myMethod() {
    guard true == true else {
        self.anotherMethod()
        return
    }
}

// Proposed
func myMethod() {
    guard true == true else {
        self.anotherMethod()
    }
}

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.

Hello Dale,

After such a change, what is this program supposed to print?

func dangerousGame() {
    for dice in [1, 2, 3, 4, 5, 6] {
        guard dice % 2 == 0 else { }
        print("Beetlejuice")
    }
}
1 Like

This example is there to emphasize that program clarity takes profit from an explicit return, or break, or continue, etc.

1 Like

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!" :wink:

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.

8 Likes

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.

6 Likes

I think it probably merits inclusion. More generally I think any pitch that makes control-flow less explicit is a very hard sell.

5 Likes