When can Swift’s return keyword be omitted?
Ever since the very first version of Swift, we’ve been able to omit the return
keyword within single-expression closures, such as this one, which attempts to convert each element within an array of String
values into an equivalent Int
:
let strings = ["1", "2", "3"]
let ints = strings.compactMap { string in
Int(string)
}
However, if a given closure contains multiple expressions, then the return
keyword can’t be omitted, even if that closure doesn’t include any conditions or separate code branches. So the closure passed into the below call to map
needs to explicitly mark its last expression as its return value:
class GameController {
private(set) var players = [Player]()
func reviveAllPlayers() {
players = players.map { player in
var player = player
player.isActive = true
player.hitPoints = 100
return player
}
}
}
To learn more about the map
and compactMap
functions used above, check out this Basics article.
In Swift 5.1, the above behavior was expanded to also include functions and computed properties, while maintaining the exact same rules. So now, when writing a function that contains just a single expression that computes a return value, then we can also omit the return
keyword — like this:
extension GameController {
func playersQualifiedForNextLevel() -> [Player] {
players.filter { player in
player.isActive && player.score > 1000
}
}
}
Above we’re actually omitting two return keywords — both the top-level one within our function, and the one for the closure passed to filter
.
Likewise, when implementing a computed property that simply returns the outcome of a single expression, then the return
keyword can now also be omitted — which often makes it possible to fit an entire computed property implementation on a single line of code, if that’s something that we’d like to do:
extension GameController {
var gameCanBeStarted: Bool { players.count > 1 }
}
Worth noting, though, is that all of these features are entirely optional. If we wanted to, we could modify each of the examples that we’ve taken a look at so far to instead always use explicit return
keywords, and everything would keep working the exact same way.
One thing that could potentially make the above behaviors slightly more confusing, however, is when we start to adopt SwiftUI. When using a closure to construct the body of a SwiftUI container, it might initially seem like we’re actually able to omit the return
keyword even within closures that contain multiple expressions or code paths — like this:
struct RootView: View {
@ObservedObject var loginController: LoginController
var body: some View {
NavigationView {
if let user = loginController.loggedInUser {
HomeView(user: user)
} else {
LoginView(handler: loginController.performLogin)
}
}
}
}
However, the above closure is not actually using multiple implicit returns, but is instead being processed by SwiftUI’s ViewBuilder
— which is a function/result builder that takes each of our view expressions and combines them into a single return type.
To illustrate, let’s see what happens if we turn the above closure into a method instead:
struct RootView: View {
@ObservedObject var loginController: LoginController
var body: some View {
NavigationView(content: makeContent)
}
private func makeContent() -> some View {
if let user = loginController.loggedInUser {
HomeView(user: user)
} else {
LoginView(handler: loginController.performLogin)
}
}
}
When attempting to compile the above code, we’ll now get the following build error:
Function declares an opaque return type, but has no return
statements in its body from which to infer an underlying type.
To fix it, we’d have to mark our new makeContent
method with the same @ViewBuilder
attribute that SwiftUI marks many of its closure parameters with — which once again makes it possible for us to declare multiple expressions without any return
keywords:
struct RootView: View {
@ObservedObject var loginController: LoginController
var body: some View {
NavigationView(content: makeContent)
}
@ViewBuilder private func makeContent() -> some View {
if let user = loginController.loggedInUser {
HomeView(user: user)
} else {
LoginView(handler: loginController.performLogin)
}
}
}
So, to sum things up:
- Swift’s
return
keyword can always be omitted within all single-expression closures, functions, and computed properties. But, if we wish, we can still use explicit returns within each of those contexts as well. - Whenever a closure, function or computed property contains multiple, top-level expressions, then we need to explicitly mark our return value expressions with the
return
keyword. - Of course, functions or closures that don’t actually return anything (or, technically, return
Void
) never need to contain anyreturn
keyword at all, unless we’d like to manually exit out of that scope — for example by performing an early return. - SwiftUI is a bit of a special case in this context, since it doesn’t rely on implicit returns (for the most part), but instead uses its
ViewBuilder
to combine all of our expressions within a given container into a single view value.