Fork me on GitHub

How Do I Write Multiple Trailing Closures in Swift?

This article presumes you already know goshdarn closure syntax.

Functions with multiple trailing closure parameters

Use standard Swift syntax to declare parameters of closure types:

func example1( value: Int, closure1: (Int) -> String, closure2: (String) -> Bool, closure3: (Bool) -> Int ) -> Int { let value1 = closure1(value) let value2 = closure2(value1) let value3 = closure3(value2) return value3 }

Swift versions 5.2 and earlier would let you “pop” only the last closure out of the function call parenthesis while all closures before it remained inside.

// Swift 5.2 and before: let value = example1( value: 420, closure1: { number in String(describing: number) }, closure2: { string in string.count.isMultiple(of: 69) } ) { boolean in boolean ? 1 : 0 }

New in Swift 5.3 and later, you can “pop” all the trailing closures outside the closing parenthesis of the function call:

// Swift 5.3 and after: let value = example1(value: 420) { number in String(describing: number) } closure2: { string in string.count.isMultiple(of: 69) } closure3: { boolean in boolean ? 1 : 0 }

The rules for closure labelling are:

Functions with omitted argument labels

You may omit the argument label while declaring a function:

func example2( _ value: Int, _ closure1: (Int) -> String, _ closure2: (String) -> Bool, _ closure3: (Bool) -> Int ) -> Int { let value1 = closure1(value) let value2 = closure2(value1) let value3 = closure3(value2) return value3 }

In Swift 5.2 and earlier, you would use the function with no argument labels:

// Swift 5.2 and before: let value = example2( 420, { number in String(describing: number) }, { string in string.count.isMultiple(of: 69) } ) { boolean in boolean ? 1 : 0 }

While using the new syntax in Swift 5.3 and after, you must use _: to label closures that were previously unlabelled.

// Swift 5.3 and after: let value = example2(420) { number in String(describing: number) } _: { string in string.count.isMultiple(of: 69) } _: { boolean in boolean ? 1 : 0 }

Functions with only closures for parameters

Declaration works exactly as above:

func example3(closure1: () -> Int, closure2: (Int) -> Bool) -> Bool { let value1 = closure1() let value2 = closure2(value1) return value2 }

While using, you can omit the function parentheses and get straight to the closures:

let value = example3 { 420 } closure2: { number in number.isMultiple(of: 69) }

Multiple trailing closures in subscripts

This syntax carries over into subscript declaration…

struct Example { private let array: [Int] init(array: [Int]) { self.array = array } subscript( index: Int, closure1: (Int) -> Int, closure2: (Int) -> Int ) -> Int { let index1 = closure1(index) let index2 = closure2(index1) return array[index2] } subscript(closure1: () -> Int, closure2: (Int) -> Int) -> Int { let index1 = closure1() let index2 = closure2(index1) return array[index2] } }

… and usage:

let example = Example(array: [0, 1, 2, 3, 4]) let value1 = example[1] { $0 * $0 } _: { $0 + $0 } let value2 = example[] { 2 } _: { $0 * $0 }

Unlike function parenthesis, the brackets that denote subscript access cannot be omitted.

If an external argument label is not provided explicitly, subscript calls will use _: instead of the parameter name. This applies to subscript calls in general, not just this specific case.

Provide an explicit external argument label…

extension Example { subscript( index index: Int, transform1 closure1: (Int) -> Int, transform2 closure2: (Int) -> Int ) -> Int { let index1 = closure1(index) let index2 = closure2(index1) return array[index2] } }

… to use labelled arguments at your call site. As usual, the label of the first trailing closure is ommitted:

let value3 = example[index: 3] { $0 + 2 } transform2: { $0 - 2 }

Closures and trailing closures

It is possible to declare closures that take multiple trailing closure parameters:

let example: (Int, (Int) -> Int, (Int) -> Int, (Int) -> Int) -> Int = { value, closure1, closure2, closure3 in let value1 = closure1(value) let value2 = closure2(value1) let value3 = closure3(value2) return value3 }

While using this closure, the first trailing closure argument label is omitted, while subsequent closures must be labelled with _: as closures do not have labelled arguments:

let value = example(42) { $0 + 2 } _: { $0 * $0 } _: { -$0 }

Default arguments and trailing closures

The behaviour for handling default arguments is unchanged. Consider the function:

func example4(closure: () -> Int = { 69 }, any: Any? = nil) { print(closure()) print(any == nil) }

Invoking it like this:

example4 { 420 }

Is the same as:

example4(any: { 420 })

Although the provided trailing closure matches the type of the closure: parameter, existing trailing closure behaviour matches the closure to any: .

See also:

Wish to access this site but with raging profanity in the URL? !@#$ingmultipletrailingclosuresyntax.com is a less work-friendly mirror.

By Prathamesh Kowarkar.

With many thanks to Zoë Smith, Zev Eisenberg, and Em Lazer-Walker.

Special thanks to John Sundell for Splash and Guilherme Rambo for hosting it online. Splash has been instrumental in syntax highlighting all the code you see here.