Could Swift's Guard Statement Work in Go?
While writing yet another if block to check whether a map had a value, I asked myself a question: could Swift’s guard statement work in Go?
Go is such an if heavy language. You’re adding if statements pretty much everywhere: after you receive an error, after you do a type assertion, when you want to verify that a map has a value. All of these come to you in the form of tuples, with the last one being the value to switch on:
thing, err := doSomething()
if err != nil {
return err
}
Having a way to switch like this within an expression would be amazing. It could be an operator that would strip out the last value and pass it through to an else block when it’s either non-nil (for errors) or false (for bools). Otherwise, it would simply return everything else:
thing := guard { doSomething() } else { return err }
I believe this is achievable in Swift using guard or guard let:
guard let thing = doSomething() else { return }
But thinking about it for more than a few minutes, I can’t really see this workable in Go. It’s easy enough for Swift to have this, with it’s nullability baked into the type system. If a value is either something or nothing, then simply returning that something as the result of an expression is a natural thing to do.
And because there’s only one variable, there’s no need to do things like declare that err is the variable to receive the last parameter. That’s right, there’s no variable declaration of err in that else branch in that first example. Could you assume that the variable name would be err if the last tuple value is of type error (likewise, for bool types, the implicit name would be ok)? Well, you could, but it’d be pretty ugly. You’re now relying on knowledge not evident in the code, a property that goes against Go’s effort to be clear.
Another problem is the use of blocks over function calls. In Go, the defer and go statements actually takes a function call. The semantics of a regular function call are the same, it’s just when the function is dispatched that differs. One could argue that in order to keep the design consistent, a guard statement would need similar semantics: a function which returns at least two results. This is probably not a deal breaker, but it would be a little limiting, and would break the ability to nest these in a single expression without making lambdas.
The closest plausible solution I could come up with is a new statement that uses this property, and combines it with an assignment and else block. Something along the lines of:
guard thing, err := doSomething() else { return err }
While this looks like it includes a regular assignment, its presence will actually be baked into the syntax, and would be used to declare the variables that would accept the function result. Once called, and the tuple returned, the statement will assign it to the declared variables, and branch to else based on the last value’s type. If it’s error, it will branch when it’s non-nil; if it’s bool, it’ll branch when it’s false (anything else will be a type error). If the branch isn’t taken, the other variables will be available to anything beyond it (this is one of the key differences with the existing assignment based if).
Because the assignment is required to declare the necessary variables, this has to be a statement, and cannot be nested. Such limitation feels critical to the usefulness of this, and if proposed, its worth in the language would probably be questioned. That’s a lot of new syntax for something that’s marginally better than an if block.
So I’m doubtful something like this will see its presence in Go anytime soon. But it’s been an interesting exercise.