Part of Swift with Copilot

Swift: Add 2 numbers using function and closure

Sandy LaneSandy Lane

Video: Swift: Add 2 numbers using function and closure by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Swift with Copilot: Add 2 Numbers — Function and Closure

Function form: func add(_ a: Int, _ b: Int) -> Int { a + b }. Closure form: let add: (Int, Int) -> Int = { $0 + $1 }. Same call site add(3, 5). Different declaration syntax; identical type.

A side-by-side that reinforces episode 13.

The Copilot prompt

// Add two numbers using both a function and a closure

Copilot completes:

// Function
func addFn(_ a: Int, _ b: Int) -> Int {
  a + b
}

// Closure
let addCl: (Int, Int) -> Int = { $0 + $1 }

print(addFn(3, 5))    // 8
print(addCl(3, 5))    // 8

Both produce 8. The call site is identical.

Function declaration syntax

func add(_ a: Int, _ b: Int) -> Int {
  return a + b
}
  • func — keyword.
  • add — name.
  • _ — suppress argument label (so add(3, 5) instead of add(a: 3, b: 5)).
  • a: Int, b: Int — parameters.
  • -> Int — return type.
  • { ... } — body.

For a single-expression body, you can omit return:

func add(_ a: Int, _ b: Int) -> Int {
  a + b
}

Closure literal syntax

let add: (Int, Int) -> Int = { (a, b) in
  return a + b
}
  • (Int, Int) -> Int — explicit type annotation.
  • { (a, b) in ... } — closure literal.
  • (a, b) — parameters; types inferred from the annotation.
  • in — separator between parameters and body.
  • return ... — optional for single-expression closures.

Compact form using $N:

let add: (Int, Int) -> Int = { $0 + $1 }

Shortest form. $0 and $1 are the first two arguments.

Inferring types

If you assign without an annotation:

let add = { (a: Int, b: Int) in a + b }

The closure parameters need types — the compiler can't infer from a literal. Add to the parameter list, and the return type is inferred.

Calling them

addFn(3, 5)    // 8
addCl(3, 5)    // 8

Same syntax. The closure variable is callable just like a function.

You can also pass either to a higher-order function:

func applyTwice(_ op: (Int, Int) -> Int, to a: Int, and b: Int) -> Int {
  op(op(a, b), op(a, b))    // ((a+b)+(a+b))
}

applyTwice(addFn, to: 3, and: 5)    // 16
applyTwice(addCl, to: 3, and: 5)    // 16
applyTwice(+, to: 3, and: 5)         // 16 — operators are functions too

Storing in a collection

let operations: [(Int, Int) -> Int] = [
  addFn,
  addCl,
  { $0 - $1 },
  *,
]

for op in operations {
  print(op(10, 3))
}
// 13 13 7 30

Functions, closures, and operators all interoperate as values.

Default arguments (function only)

func add(_ a: Int, _ b: Int = 1) -> Int {
  a + b
}

add(5)        // 6
add(5, 3)     // 8

Closures don't support default arguments. If you need defaults, use a function.

Argument labels (function only)

func subtract(from total: Int, value: Int) -> Int {
  total - value
}

subtract(from: 10, value: 3)    // 7

Closures have no argument labels — always positional.

For self-documenting code, functions are better. For pipelines, closures' brevity wins.

When to choose which

Function: - Named, reusable. - Documentation matters. - Default arguments needed. - Argument labels improve readability. - Recursive (closures can reference themselves only via tricks).

Closure: - One-line transform inside map/filter/reduce. - Inline callback (onTap: { ... }). - Capturing surrounding state. - Short and used once.

For "add two numbers as a public API," a function:

public func add(_ a: Int, _ b: Int) -> Int { a + b }

For "I need a callback that adds 5":

let addFive: (Int) -> Int = { $0 + 5 }

Generic versions

To accept any numeric type:

func add<T: Numeric>(_ a: T, _ b: T) -> T {
  a + b
}

add(3, 5)        // 8 (Int)
add(3.5, 1.5)    // 5.0 (Double)

Closures don't support generic parameters as cleanly:

// You'd write a function that returns a closure
func makeAdder<T: Numeric>() -> (T, T) -> T {
  return { $0 + $1 }
}

let addInt: (Int, Int) -> Int = makeAdder()
let addDbl: (Double, Double) -> Double = makeAdder()

For generics, prefer functions.

Common stumbles

Closure type without parens. (Int, Int) -> Int — the parens around the input types are required, even for one parameter ((Int) -> Int).

return in single-expression closure. Optional. { $0 + $1 } works without it.

Closure can't be recursive. A closure can't reference itself by name (until it's fully defined). For recursion, use a function.

Default args in closures. Not supported. Use a function or a wrapper.

Argument labels in closures. Not supported. Always positional.

Function reference syntax. let f = add(_:_:) (with the _:_: for parameters). Useful when there are overloads to disambiguate.

What's next

Episode 15: Add 1 to each element in an array. map for transforms.

Recap

Function: func add(_ a: Int, _ b: Int) -> Int { a + b }. Closure: let add: (Int, Int) -> Int = { $0 + $1 }. Both have type (Int, Int) -> Int; both callable identically. Functions support default args, labels, generics, recursion. Closures support compact syntax, capture, inline use. Pick based on context — named/reusable → function; inline/one-shot → closure.

Next episode: map +1.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.