Skip to content

Instantly share code, notes, and snippets.

@gringoireDM
Last active March 25, 2020 04:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gringoireDM/1f18f3bb4e74e2d914a748d89486db56 to your computer and use it in GitHub Desktop.
Save gringoireDM/1f18f3bb4e74e2d914a748d89486db56 to your computer and use it in GitHub Desktop.
@escapable optional closures

Allow @escaping for optional closures as functions parameters

Introduction

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns . When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.

Quote from Swift documentation

This statement is not always true when considering optional closures passed as function parameters.

This proposal aims to uniform the behaviour of optional closures with the well-known behaviour of non-optional closures when passed as function parameters.

Swift-evolution thread: Allowing @escaping for optional closures in method signature

Motivation

Since swift 3, closures in function parameters are by default non-escaping: when a closure is passed as a function parameter, then this closure will be synchronously used within the function scope before it returns.

func nonescaping(closure: () -> Void)
func escaping(closure: @escaping () -> Void)

The swift documentation currently doesn't cover the case where an optional closure is accepted as a function parameter.

func foo(closure: (() -> Void)? = nil)

In this case the closure is implicitly escaping because it is stored as associated value of the case .some of Optional. But is that closure intended to escape the actual function scope?

Is this closure executed before or after the function's return?

This is an information that remains unknown without knowing the actual function implementation, or without the help of additional documentation provided by the developer.

func foo(closure: (() -> Void)? = nil) {
    ....
    closure?()
    ...
}

is therefore equivalent to

func foo(closure: @escaping () -> Void = { }) {
    ....
    closure?()
    ...
}

with the exception that closure is not escaping foo in the second case, and if it wasn't for the Optional, closure wasn't escaping foo in the first case either.

Besides, the fact that for optional the escapability is not explicitly defined by the usual @escaping keyword, brought many developers to think that it isn't possible to make an optional closure escapable, having many newcomers to the language in confusion after reading the documentation.

Proposed solution

Escape analysis should be extended to optional parameter functions to allow them to be explicitly marked as @escaping in case they will escape the function scope.

Having Optional closures uniformed to non-optional closures behavior will reduce the confusion caused by the documentation not covering this case. Also it will provide to users more explicit and expressive APIs. Optional closures not marked as @escaping should be by default non-escaping to match the existing behavior of non-optional closures.

This is a source breaking change: Changing the default will introduce a change in the contract on the functions that have optional closures as parameters, and it is therefore source breaking for both the implementation and the clients of the function.

Detailed design

func foo(closure: @escaping (() -> Void)? = nil) // escaping
func foo(closure: (() -> Void)? = nil) // non-escaping

Having the closure to be Optional will no longer cause, as side effect, the closure to implicitly escape. The escape analysis would be extended to Optional parameter functions so that they can be explicitly marked as @escaping where appropriate.

To maintain source compatibility, a new build settings can be Introduced to keep using the legacy behaviour for the optional closures, similarly to what was previously done in swift4 migration with the SWIFT_SWIFT3_OBJC_INFERENCE build settings for the @objc automatic inference.

Source compatibility

Changing the default of Optional closures from being escaping to non-escaping will introduce a a change to the contract on the function, which is potentially source-breaking for both implementations and clients of the function.

Effect on ABI stability

Default for optional closures will have to change from being escaping to non-escaping.

Effect on API resilience

This feature can be added without breaking ABI just by introducing @nonescaping, extending escape analysis to optional parameter functions to allow them to be explicitly marked as @nonescaping, without changing the default behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment