9. Functions¶
What & why¶
Functions package reusable logic behind a name. Swift's functions are unusually expressive: arguments carry labels so call sites read like sentences (move(from: a, to: b)), and functions are first-class values you can pass around.
Defining and calling¶
func greet(name: String) -> String {
return "Hello, \(name)!"
}
print(greet(name: "Ada")) // Hello, Ada!
func name(parameters) -> ReturnType { ... }- A function with no
->returnsVoid(nothing). - If the body is a single expression,
returnis optional:
Argument labels¶
Each parameter has an argument label (used by the caller) and a parameter name (used inside):
func move(from start: Int, to end: Int) {
print("moving from \(start) to \(end)") // inside: start, end
}
move(from: 3, to: 9) // outside: from, to
Write _ as the label to drop it:
If you write just one name, it serves as both label and parameter name (greet(name:) above). Swift's API Design Guidelines favour labels that make a call read naturally: insert(x, at: 0), remove(at: 2), contains(where:).
Default values¶
func connect(host: String, port: Int = 443, secure: Bool = true) {
print("\(secure ? "https" : "http")://\(host):\(port)")
}
connect(host: "example.com") // https://example.com:443
connect(host: "localhost", port: 8080, secure: false)
connect(host: "example.com", secure: false) // skip port, keep its default
Returning multiple values: tuples¶
func minMax(of numbers: [Int]) -> (min: Int, max: Int)? {
guard var lo = numbers.first else { return nil }
var hi = lo
for n in numbers.dropFirst() {
lo = min(lo, n)
hi = max(hi, n)
}
return (lo, hi)
}
if let result = minMax(of: [4, 9, 1, 7]) {
print(result.min, result.max) // 1 9
}
Variadic parameters¶
func average(_ numbers: Double...) -> Double {
numbers.isEmpty ? 0 : numbers.reduce(0, +) / Double(numbers.count)
}
print(average(2, 4, 9)) // 5.0
Inside the function, numbers is an [Double].
Parameters are constants — inout for changing them¶
func double(_ x: inout Int) {
x *= 2
}
var n = 21
double(&n) // the & marks the argument as mutable
print(n) // 42
Use inout sparingly; returning a new value is usually clearer. The standard library uses it for things like swap(&a, &b).
Functions as values¶
A function has a type, written (ParamTypes) -> ReturnType:
func triple(_ x: Int) -> Int { x * 3 }
let f: (Int) -> Int = triple
print(f(4)) // 12
print([1, 2, 3].map(triple)) // [3, 6, 9]
func makeAdder(_ amount: Int) -> (Int) -> Int {
return { value in value + amount } // a closure capturing `amount`
}
let addTen = makeAdder(10)
print(addTen(5)) // 15
Closures (the { value in ... } syntax) get their own lesson in Intermediate.
Nested functions¶
Functions can live inside functions, which is great for private helpers:
func process(_ values: [Int]) -> Int {
func isValid(_ v: Int) -> Bool { v > 0 }
return values.filter(isValid).count
}
Recursion¶
Every recursive function needs a base case. Swift does not guarantee tail-call optimization; very deep recursion (tens of thousands of frames) can overflow the stack — convert to a loop when depth is unbounded.
Overloading¶
Functions can share a name if their parameter types, labels, or count differ:
func describe(_ x: Int) -> String { "int \(x)" }
func describe(_ x: String) -> String { "string \(x)" }
func describe(_ x: Int, times: Int) -> String { String(repeating: "\(x) ", count: times) }
The return type alone isn't enough to distinguish overloads in most cases.
Documenting functions¶
/// Returns the average of `values`, or `nil` when the array is empty.
///
/// - Parameter values: The numbers to average.
/// - Returns: The arithmetic mean.
func mean(_ values: [Double]) -> Double? {
guard !values.isEmpty else { return nil }
return values.reduce(0, +) / Double(values.count)
}
Option-click a function name in Xcode, or run DocC, to see these rendered.
@discardableResult¶
If callers are allowed to ignore a return value, say so — otherwise the compiler warns:
@discardableResult
func logAndReturn(_ message: String) -> String {
print(message)
return message
}
logAndReturn("hi") // no "unused result" warning
Common mistakes¶
- Forgetting argument labels at the call site (
greet("Ada")instead ofgreet(name: "Ada")). - Trying to modify a parameter — parameters are constants; copy into a
varor useinout. - Forgetting
&when passing to aninoutparameter. - Missing base case in recursion.
- Overusing
inoutand tuples where a smallstructwould be clearer. - Ignoring return values accidentally — the warning is there for a reason.
Exercises¶
- Write
func isEven(_ n: Int) -> Booland use it withfilteron1...20. - Write
func fullName(first: String, middle: String? = nil, last: String) -> String. - Write
func stats(of numbers: [Double]) -> (mean: Double, min: Double, max: Double)?. - Write
swapValuesfor twoInts withinout, then check howswap(&a, &b)compares. - Implement
fibonacci(_ n: Int) -> Intrecursively, then iteratively. Time both forn = 35. - Write
func makeCounter() -> () -> Intthat returns a function that returns 1, 2, 3… on successive calls. - Overload
describeto handleInt,Double,[Int].
Recap¶
func name(label param: Type = default) -> Result; single-expression bodies skipreturn.- Argument labels make call sites read like prose;
_removes a label. - Return tuples for several values;
Type...for variadics;inout+&to mutate in place. - Functions are values with types like
(Int) -> Int. - Recursion needs a base case; overloads are resolved by labels and types.
Next: Optionals →