Functions
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Every Swift function has both a name and, distinctly, an external calling convention — its argument labels — which is what lets the same operation read naturally at the call site (greet(person:)) while keeping a short,
purely internal parameter name to work with inside the function body.
Defining and Calling Functions
func greet(person: String) -> String {
"Hello, " + person + "!" // a single-expression body may omit `return` (Swift 5.1+)
}
print(greet(person: "Anna"))
func greetAgain(person: String) -> String {
return "Hello again, " + person + "!" // an explicit `return` is always allowed too
}
func doNothing() { } // no parameters, and no `-> Type` means it returns Void
Parameters and Return Values
func minMax(array: [Int]) -> (min: Int, max: Int) { // a tuple return communicates two results without an out-param
var currentMin = array[0]
var currentMax = array[0]
for value in array[1...] {
if value < currentMin { currentMin = value }
else if value > currentMax { currentMax = value }
}
return (currentMin, currentMax)
}
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)") // the tuple's labels are usable at the call site
func greetOptionally(person: String) -> String? { // an Optional return communicates "may fail"
person.isEmpty ? nil : "Hello, \(person)!"
}
A function with no → Type still technically returns Void (the empty tuple ()), which is why doNothing()
above compiles without a return at all.
Argument Labels vs. Parameter Names
func greet(person: String, from hometown: String) -> String { // "from" is the label, "hometown" the parameter name
"Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino")) // call site reads "from Cupertino"
func multiply(_ number: Int, by factor: Int) -> Int { // `_` suppresses the label entirely
number * factor
}
print(multiply(4, by: 3)) // reads like prose, no label on the first argument
A parameter’s argument label is what callers write; its parameter name is what the function body uses — they default to the same word, but naming them separately (from hometown:) or omitting the label (_ number:)
is what makes Swift APIs read as grammatical phrases rather than positional argument lists.
Default Parameter Values
func greeting(for person: String, formally isFormal: Bool = false) -> String {
isFormal ? "Good day, \(person)." : "Hey \(person)!"
}
print(greeting(for: "Sam")) // uses the default: false
print(greeting(for: "Sam", formally: true)) // overrides it
// Convention: place parameters with a default value after parameters without one, so a call
// omitting the default reads naturally without needing every earlier label spelled out.
Variadic Parameters
func arithmeticMean(_ numbers: Double...) -> Double { // accepts zero or more Doubles, exposed as [Double]
var total = 0.0
for number in numbers { total += number }
return numbers.isEmpty ? 0 : total / Double(numbers.count)
}
print(arithmeticMean(1, 2, 3, 4, 5)) // 3.0
print(arithmeticMean()) // 0.0 -- zero arguments is allowed
A function may declare at most one variadic parameter, and any parameter written after it must use an argument label so the compiler can tell where the variadic list ends and the next parameter begins.
inout Parameters and Copy-In Copy-Out
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt) // `&` is required at every inout call site, as a visual flag
print(someInt, anotherInt) // 107 3
Conceptually, an inout argument is copied in to the function on call, and copied back out to overwrite the
original the moment the function returns (in practice the compiler may pass a direct reference instead when
that’s provably equivalent, but the copy-in/copy-out model is what the language guarantees, and is why an
inout argument cannot alias another argument or captured variable the function also uses).
borrowing and consuming Parameter Modifiers
func printReport(_ report: borrowing Report) { // borrowing: read-only access, no ownership transfer
print(report.summary)
} // ownership stays with the caller after this returns
func archive(_ report: consuming Report) { // consuming: takes ownership, caller's copy is done
Archive.shared.store(report)
} // the caller can no longer use `report` after passing it
struct Report { let summary: String }
borrowing and consuming make Swift’s default implicit-copy parameter-passing convention explicit and, where
the compiler can prove it safe, avoid a retain/copy that the default convention would otherwise perform — they
matter most for large value types and for noncopyable (~Copyable) types, covered fully in
Structures and Classes.
Function Types
func addTwoInts(_ a: Int, _ b: Int) -> Int { a + b }
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int { a * b }
var mathFunction: (Int, Int) -> Int = addTwoInts // the type (Int, Int) -> Int names a function's shape
mathFunction = multiplyTwoInts // any matching function value can be assigned
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) { // as a parameter
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
func stepFunction(backward: Bool) -> (Int) -> Int { // as a return value
func stepForward(_ input: Int) -> Int { input + 1 }
func stepBackward(_ input: Int) -> Int { input - 1 }
return backward ? stepBackward : stepForward
}
Nested Functions
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(_ input: Int) -> Int { input + 1 } // visible only inside chooseStepFunction
func stepBackward(_ input: Int) -> Int { input - 1 }
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
while currentValue != 0 {
currentValue = moveNearerToZero(currentValue)
}
A nested function is hidden from the outside world by default, yet can still be returned or passed out of its
enclosing function — as stepForward/stepBackward are above — letting the enclosing function expose a
capability without exposing its implementation as separate top-level declarations.
@discardableResult
@discardableResult
func markAttendance(for name: String) -> Bool {
print("\(name) marked present")
return true
}
markAttendance(for: "Priya") // fine -- no "unused result" warning, unlike an ordinary non-Void return
Without @discardableResult, ignoring a non-Void return value still compiles but produces a warning; the
attribute is an explicit statement by the function’s author that the caller is allowed to ignore it, typically
because the return value is a convenience (e.g. "did this succeed") rather than the point of calling the
function.
Overloading
func describe(_ value: Int) -> String { "an Int: \(value)" }
func describe(_ value: String) -> String { "a String: \(value)" }
func describe(_ value: Int, radix: Int) -> String { String(value, radix: radix) } // differs by parameter list too
print(describe(42)) // picks the Int overload
print(describe("hi")) // picks the String overload
Two functions may share a name as long as they differ in parameter types, parameter count, or argument labels (return type alone is not enough to disambiguate a call); the compiler resolves each call to exactly one overload using the static types available at the call site.
rethrows, async, and throws Signatures in Outline
func execute(_ operation: () throws -> Void) rethrows { // rethrows: only throws if `operation` itself throws
try operation()
}
func fetchValue() async throws -> Int { // async and throws compose, in that order
try await Task.sleep(for: .seconds(1))
return 42
}
throws marks a function that can propagate an error and must be called with try; rethrows marks a function
that only throws when a closure/function parameter it was given throws, letting callers that pass a
non-throwing closure call it without try; async marks a function whose body may suspend and must be called
with await. All three are covered at full depth in
Error Handling and
Async/Await and Tasks respectively — this page
only establishes how they sit in a function’s type signature.
See Also
-
Closures — unnamed function values, and how they relate to the named functions on this page.
-
Error Handling —
throws/try/catchin full. -
Async/Await and Tasks —
async/awaitin full. -
Structures and Classes —
borrowing/consuming/consumeand noncopyable types in depth. -
Generics — writing a function generic over its parameter and return types.