Skip to content

Instantly share code, notes, and snippets.

@erica
Last active March 20, 2024 08:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save erica/3731e24fc252c8e66850e0e02f491281 to your computer and use it in GitHub Desktop.
Save erica/3731e24fc252c8e66850e0e02f491281 to your computer and use it in GitHub Desktop.

Enhancing closure argument flexibility

Introduction

This proposal loosens closure requirements to support developer flexibility. It removes the _ in requirement that bypasses explicit argument use and allow closures to use any, all, or none of the implicit $n variables as needed or desired.

The Swift-evolution thread about this topic can be found here: Removing "_ in" from empty closures

Motivation

Swift closures that do not explicitly declare an internal parameter list must reference all arguments using implicit $n shorthand names. If they do not, Swift complains that the contextual type for the closure argument "expects n arguments, which cannot be implicitly ignored." This requirement diminishes the efficacy of Swift's $n syntactic sugar. Eliminating the requirement means:

  • {} becomes a valid 'noop' closure in any context requiring a Void-returning closure.
  • Implementations can discard unnecessary code cruft and streamline their minimum implementation from { _(, _)* in } to {}.
  • { expression } becomes a valid closure in any context requiring a return value. The expression can offer a simple expression or literal, such as { 42 }.
  • The closure can mention some of its parameters without having to mention all of its parameters.

Detailed Design

Under this proposal, parameterized closures types will automatically promote closures that mention a possibly empty subset of implicit arguments. All the following examples will be valid and compile without error or warning:

let _: () -> Void = {}
let _: (Int) -> Void = {} 
let _: (Int, Int) -> Int = { 5 }
let _: (Int, Int) -> Int = { $0 }
let _: (Int, Int) -> Int = { $1 }

In the first two examples, the empty closure literal {} will autopromote to satisfy any void-returning closure type (T...) -> Void.

// Current
doThing(withCompletion: { _ in })
let x: (T) -> Void = { _ in }

// Proposed
doThing(withCompletion: {})
let x: (T) -> Void = {}

In the remaining examples, the closure will support the return of any expression, whether or not it mentions any or all of the implicit arguments.

Impact on Existing Code

This proposal offers no negative effect on existing code, which will continue to compile and run as it did prior to adoption. It offers positive improvements for new code added after adoptions. We believe it would be beneficial for Xcode to scan for {_(, _)* in} patterns during migration and offer fixits to update this code to {}.

Alternatives Considered

We considered and discarded a policy where Swift encourages the use of optional closures in place of simplifying the {} case. This approach does not scale to non-Void cases. It does not align with Cocoa APIs, where most completion handlers are not optional.

Related Proposals

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