Functions and closures
Swift functions are first-class values with substantial signature flexibility: parameter labels, default values, variadic parameters, in-out parameters, throwing (and async) modifiers, and generic type parameters. Closures are anonymous functions — substantial in idiomatic Swift code (many APIs accept closures as arguments). The conventional Swift closure surface admits trailing closure syntax, shorthand parameter names ($0, $1), capture lists for explicit reference behaviour, and @escaping / @autoclosure attributes. The combination — labelled parameters, the substantial closure surface, the trailing-closure form, the type-system integration — is the substance of Swift’s function surface.
Function declarations
The principal form:
func name(parameters) -> ReturnType {
body
}
Examples:
func add(_ a: Int, _ b: Int) -> Int {
a + b // implicit return for single-expression
}
func greet(name: String) -> String {
return "Hello, \(name)"
}
func performAction() { // no return value (Void / ())
print("acting")
}
The _ (wildcard) suppresses the parameter label at the call site:
func add(_ a: Int, _ b: Int) -> Int { a + b }
add(3, 4) // no labels needed
func send(message: String, to recipient: String) {
/* ... */
}
send(message: "hi", to: "Alice") // labels visible
Parameter labels
Swift admits distinct argument labels and parameter names:
func send(message msg: String, to recipient: String) {
print("\(msg) → \(recipient)")
}
send(message: "hi", to: "Alice") // call site sees labels
The label is what the caller writes; the parameter name is what the implementation uses.
When the same name is acceptable, only one is needed:
func send(message: String, to recipient: String) {
print("\(message) → \(recipient)") // message is both
}
The conventional API design uses substantive labels:
func insert(_ item: Item, at index: Int)
// ^ ^
// no label "at" label
source.insert(item, at: 0)
Default arguments
func greet(name: String = "world", greeting: String = "Hello") {
print("\(greeting), \(name)")
}
greet() // "Hello, world"
greet(name: "Alice") // "Hello, Alice"
greet(name: "Alice", greeting: "Hi")
Default values are admitted; the conventional discipline uses defaults freely.
Variadic parameters
The ... admits any number of arguments:
func sum(_ nums: Int...) -> Int {
nums.reduce(0, +)
}
sum() // 0
sum(1, 2, 3) // 6
Inside the function, nums is [Int]. For passing an array:
let arr = [1, 2, 3]
sum(arr) // ERROR: arr is [Int], expected Int...
Pre-Swift 6, no spread-into-variadic; conventional alternatives:
// Define a non-variadic alternative:
func sum(_ nums: [Int]) -> Int {
nums.reduce(0, +)
}
// Or use the array directly inside the variadic version:
func sum(_ nums: Int...) -> Int { sum(nums) } // delegate to array form
In-out parameters
The inout admits modifying the caller’s value:
func swap<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 1
var y = 2
swap(&x, &y) // & at call site
print(x, y) // 2 1
The mechanism admits substantial efficiency for substantial structs without copying.
The standard library’s swap(_:_:) is the conventional form.
Throwing functions
Functions that may throw errors:
enum ParseError: Error {
case empty
case invalid(reason: String)
}
func parse(_ input: String) throws -> Int {
guard !input.isEmpty else { throw ParseError.empty }
guard let n = Int(input) else { throw ParseError.invalid(reason: "not numeric") }
return n
}
do {
let n = try parse("42")
print(n)
} catch ParseError.empty {
print("empty")
} catch ParseError.invalid(let reason) {
print("invalid: \(reason)")
} catch {
print("other: \(error)")
}
Treated in Error handling.
Async functions
Functions that suspend execution:
func fetchData(from url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
let data = try await fetchData(from: url)
Treated in Concurrency.
Generic functions
Type parameters in angle brackets:
func first<T>(in array: [T]) -> T? {
array.first
}
func max<T: Comparable>(_ a: T, _ b: T) -> T {
a > b ? a : b
}
Treated in Generics.
Closures
Anonymous functions:
let add: (Int, Int) -> Int = { a, b in
a + b
}
let result = add(3, 4) // 7
The form: { parameters in body }. The closure type is (In) -> Out.
Examples in idiomatic use:
[1, 2, 3].map { x in x * 2 } // explicit name
[1, 2, 3].map { $0 * 2 } // shorthand
[1, 2, 3].sorted { a, b in a > b }
[1, 2, 3].filter { $0 > 1 }
[1, 2, 3].reduce(0, +) // function reference
The shorthand parameter names $0, $1, $2 admit substantial conciseness.
Trailing closures
The trailing closure syntax — admit closures outside the parentheses if they are the last argument:
[1, 2, 3].map { $0 * 2 } // trailing closure
UIView.animate(withDuration: 0.5) {
view.alpha = 0
} completion: { _ in
print("done")
} // multiple trailing closures (Swift 5.3+)
The mechanism admits substantial DSL-like syntax — conventional in SwiftUI, Combine, etc.
Capture lists
Closures may capture variables from the enclosing scope; the capture list admits explicit capture behaviour:
class ViewController {
var count = 0
func setupHandler() {
button.action = { [weak self] in
self?.count += 1
}
}
}
The capture list [weak self] admits weak capture — avoiding retain cycles.
For value-capture:
let n = 42
let f = { [n] in print(n) } // captures n by value
Treated in Memory and ARC.
@escaping closures
Closures that outlive the calling function require the @escaping attribute:
class Server {
private var handlers: [(Request) -> Response] = []
func register(handler: @escaping (Request) -> Response) {
handlers.append(handler)
}
}
Without @escaping, the closure must run during the function call. The mechanism admits substantial type-safety around closure lifetimes.
@autoclosure
@autoclosure admits passing an expression that is wrapped into a closure automatically:
func evaluate(condition: () -> Bool) {
if condition() { print("true") }
}
evaluate { x > 0 } // explicit closure
func evaluate(condition: @autoclosure () -> Bool) {
if condition() { print("true") }
}
evaluate(x > 0) // expression auto-wrapped
The mechanism admits substantial DSL-style syntax. The standard library’s assert(_:_:) and ?? use @autoclosure for the right-hand operand.
Function types
Functions are first-class values; their type is (In) -> Out:
let add: (Int, Int) -> Int = { $0 + $1 }
let greet: (String) -> Void = { print("Hello, \($0)") }
let supplier: () -> Int = { 42 }
// Function-typed parameters:
func apply(_ f: (Int) -> Int, to x: Int) -> Int {
f(x)
}
apply({ $0 * 2 }, to: 5) // 10
// Returning a function:
func makeAdder(_ n: Int) -> (Int) -> Int {
return { $0 + n }
}
let add5 = makeAdder(5)
add5(3) // 8
Higher-order functions
The standard library admits substantial higher-order functions:
arr.map { $0 * 2 }
arr.filter { $0 > 0 }
arr.reduce(0, +)
arr.compactMap { Int($0) }
arr.flatMap { [$0, $0 * 2] }
arr.sorted(by: <)
arr.first(where: { $0.isActive })
arr.allSatisfy(\.isValid) // key path syntax
Method references and key paths
Function references admit substantial conciseness:
[1, 2, 3].map(String.init) // ["1", "2", "3"]
strings.compactMap { Int($0) } // explicit closure
strings.compactMap(Int.init) // function reference
// Key paths:
people.sorted { $0.age < $1.age } // explicit
people.sorted(by: { $0.age < $1.age })
people.sorted(using: KeyPathComparator(\.age)) // key-path comparator
The \.path syntax admits key paths — first-class references to property paths:
let nameKeyPath = \Person.name
let person = Person(name: "Alice", age: 30)
let name = person[keyPath: nameKeyPath] // "Alice"
Common patterns
Default + named labels
func fetch(url: URL,
method: String = "GET",
headers: [String: String] = [:],
timeout: TimeInterval = 30) async throws -> Data {
/* ... */
}
let data = try await fetch(url: url)
let data = try await fetch(url: url, method: "POST")
let data = try await fetch(url: url, headers: ["Authorization": token], timeout: 10)
Higher-order function
func retry<T>(attempts: Int = 3, _ operation: () throws -> T) throws -> T {
var lastError: Error?
for _ in 0..<attempts {
do {
return try operation()
} catch {
lastError = error
}
}
throw lastError!
}
let result = try retry(attempts: 5) {
try fetchData()
}
Builder pattern via methods
struct URLBuilder {
private var components = URLComponents()
func scheme(_ s: String) -> URLBuilder {
var b = self
b.components.scheme = s
return b
}
func host(_ h: String) -> URLBuilder {
var b = self
b.components.host = h
return b
}
func path(_ p: String) -> URLBuilder {
var b = self
b.components.path = p
return b
}
func build() -> URL? {
components.url
}
}
let url = URLBuilder()
.scheme("https")
.host("example.com")
.path("/api/users")
.build()
Throwing closure with retry
func with<T>(retries: Int, base delay: TimeInterval = 1.0,
_ operation: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<retries {
do {
return try await operation()
} catch {
lastError = error
if attempt < retries - 1 {
try await Task.sleep(for: .seconds(delay * pow(2.0, Double(attempt))))
}
}
}
throw lastError!
}
Function as parameter type
typealias Predicate<T> = (T) -> Bool
extension Sequence {
func count(where predicate: Predicate<Element>) -> Int {
reduce(0) { count, element in
count + (predicate(element) ? 1 : 0)
}
}
}
[1, 2, 3, 4, 5].count(where: { $0.isMultiple(of: 2) }) // 2
Currying via closures
func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
return { a in { b in f(a, b) } }
}
let add: (Int, Int) -> Int = (+)
let add5 = curry(add)(5)
print(add5(3)) // 8
Capture list with multiple variants
class Service {
var counter = 0
weak var delegate: ServiceDelegate?
func setup() {
completion = { [weak self, weak delegate] in
self?.counter += 1
delegate?.didComplete()
}
}
}
Method reference in higher-order function
let numbers = ["1", "two", "3", "four", "5"]
let parsed = numbers.compactMap(Int.init) // [1, 3, 5]
let people = [...]
let names = people.map(\.name) // key path
let sorted = people.sorted(by: { $0.age < $1.age })
Returning a closure
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2
The pattern admits substantial state encapsulation.
@autoclosure for lazy evaluation
func cache<T>(_ key: String, compute: @autoclosure () -> T) -> T {
if let cached = lookup(key) as? T { return cached }
let value = compute()
store(key, value)
return value
}
let result = cache("expensive") { computeExpensive() }
let result = cache("simple", compute: 42) // expression auto-wrapped
Result builder for DSL
@resultBuilder
struct ListBuilder {
static func buildBlock(_ items: String...) -> [String] {
Array(items)
}
}
func makeList(@ListBuilder _ builder: () -> [String]) -> [String] {
builder()
}
let list = makeList {
"first"
"second"
"third"
} // ["first", "second", "third"]
Treated in Property wrappers.
inout for modifying
func increment(_ value: inout Int, by delta: Int = 1) {
value += delta
}
var count = 0
increment(&count) // 1
increment(&count, by: 10) // 11
Generic function with constraints
func min<T: Comparable>(_ values: T...) -> T? {
values.min()
}
let m = min(3, 1, 4, 1, 5, 9) // Optional(1)
let s = min("banana", "apple", "cherry") // Optional("apple")
Variadic with closure
func combineAll<T, R>(
_ values: T...,
using combine: (T, T) -> R
) -> [R] {
var result: [R] = []
for i in 0..<(values.count - 1) {
result.append(combine(values[i], values[i+1]))
}
return result
}
A note on Void and ()
The Void type is an alias for the empty tuple ():
typealias Void = ()
func performAction() { // returns Void / ()
print("done")
}
func performAction() -> Void { /* ... */ } // explicit
func performAction() -> () { /* ... */ } // equivalent
The conventional discipline omits the explicit Void return type.
A note on the conventional discipline
The contemporary Swift functions advice:
- Use parameter labels generously — admit self-documenting calls.
- Use
_for unlabelled parameters (e.g., binary operators on values). - Use defaults freely.
- Use variadics for genuinely variadic functions; arrays for the common case.
- Use
inoutsparingly — for explicit in-place modification. - Use
throwsfor errors. - Use
asyncfor asynchrony. - Use generics for genuinely generic code.
- Use trailing closures for callbacks and DSLs.
- Use shorthand parameter names (
$0,$1) for short closures. - Use capture lists (
[weak self]) in closures stored on instances. - Use key paths (
\.path) for property references. - Use
@autoclosuresparingly — for substantial DSL benefit.
The combination — labelled parameters, defaults, variadics, in-out, generics, the substantial closure surface (trailing, capture lists, autoclosure), the function-as-value mechanism, key paths — is the substance of Swift’s function surface. The discipline produces clear, type-safe, expressive function code with substantial flexibility for substantial functional patterns.