Skip to content

Functions ​

In Basic Operators and Functions, you learned how to use functions, like sqrt and isMultiple(of:). In this chapter, you’ll learn how you can declare and use your own functions.

A function is a reusable piece of code. It can have parameters that configure its behavior, it performs a task, and it can return a result.

For example:

swift
sin(0.5 * .pi)

This code calls the sin function, which has a single parameter, an angle. In this example, the function calculates and returns the sine of the angle 0.5 * .pi.

Declaring and calling a function ​

The following example declares a function named printGreeting:

swift
func printGreeting() {
  print("Hello")
}

This function does not have any parameters and does not return a value. It simply prints a greeting.

A function declaration consists of the keyword func, a name for the function, a parameter list, and a body. Parentheses are required around the parameter list, even when it’s empty.

Note

Function names use camel case, just like constants and variables.

To call a function, you use its name:

swift
printGreeting()

A function call always includes parentheses, even when the function has no parameters.

When you call a function, the control process jumps from the function call into the body of the function. After it finishes executing this body, it returns back to where it was and continues executing the code.

Parameters and arguments ​

Parameters make a function reusable. They provide input the function can use to configure its behavior or calculate its result. The following function declares a parameter that configures a greeting:

swift
func sayHello(person: String) {
  print("Hello, \(person)")
}

Parameters are local constants scoped to the body of the function. You declare a parameter just like a constant. However, you don’t use the keyword let because parameters can only be constants, not variables.

To declare multiple parameters, you separate them with commas:

swift
func greet(person: String, greeting: String) {
  print("\(greeting), \(person)")
}

When you call a function that has parameters, you provide a value, known as an argument, for each of the parameters. For example:

swift
sayHello(person: "Steven")

This calls the function sayHello and assigns the argument "Steven" to person. As you can see, you include the name of the parameter as an argument label in the function call.

Argument labels add clarity to a function call and make it clear which argument is being assigned to which parameter. However, this doesn’t mean you can reorder them. The order of the arguments must match the order of the parameters in the function declaration.

The following example is invalid:

swift
greet(greeting: "Hello", person: "Steven")  

Return values ​

A function can return a value. You specify the type of this value in the function declaration:

swift
func max(a: Int, b: Int) -> Int {
  let result = a > b ? a : b
  return result
}

This function has a return type of Int. Inside the body, you use a return statement to specify the return value and end the function.

When you call a function that has a return value, Swift replaces the function call with its return value. The following example calls max and assigns its return value to result:

swift
let result = max(a: 4, b: 2)

You can also use the return statement in functions that don’t return a value. In this case, it simply ends the function. For example:

swift
var currentUser = "Alice"

func switchUser(user: String) {
  if user == currentUser {
    return
  }
  currentUser = user
  print("Welcome \(user)")
}

This function does nothing when you call it with a user that’s already the currentUser.

You can achieve the same result by inverting the condition of the if statement:

swift
func switchUser(user: String) {
  if user != currentUser {
    currentUser = user
    print("Welcome \(user)")
  }
}

However, this solution requires additional indentation. In general, you want to keep the main path through your code unindented to improve its readability. An early return statement helps achieve this.

Implicit return statements ​

A function can often calculate its return value with a single line of code. For example, you can shorten the declaration of max to:

swift
func max(a: Int, b: Int) -> Int {
  return a > b ? a : b
}

When a function consists entirely of a return statement, you can remove the return keyword and specify only the return value:

swift
func max(a: Int, b: Int) -> Int {
  a > b ? a : b
}

This function has an implicit return statement.

Argument labels ​

Your goal when naming a function and its parameters is to achieve clarity at the point of use. In other words, a function call should read like a sentence. Argument labels play a significant role in this.

You can configure an argument label by declaring two names for a parameter. The first name becomes the argument label; the second is used in the function body. For example:

swift
func sayHello(to person: String) {
  print("Hello, \(person)")
}

This function has a parameter with an internal name of person and an external name of to. You call this function as follows:

swift
sayHello(to: "my little friend")

You can make a similar improvement to the switchUser function:

swift
func switchUser(to user: String) {
  if user == currentUser {
    return
  }
  currentUser = user
  print("Welcome \(user)")
}

switchUser(to: "Bob")

When argument labels add clutter, not clarity, you can remove them by using the wildcard pattern (_) as an external name. In the following example, the first parameter is the direct object of the sentence, and doesn’t require an argument label:

swift
func greet(_ person: String, with greeting: String) {
  print("\(greeting), \(person)")
}

greet("Steven", with: "Hello")

In the case of the function max, the parameter names, whether they be a and b, or x and y, are of no importance to the caller of the function, so you should remove their argument labels:

swift
func max(_ a: Int, _ b: Int) -> Int {
  a > b ? a : b
}

With this declaration, a call to max becomes max(4, 2), which is just as clear as max(a: 4, b: 2), but less verbose and easier to read.

Note

To help with clear and consistent naming, Swift defines a set of API Design Guidelines. This course follows these guidelines and attempts to teach by example.

When referring to a function by name, you can include the argument labels, even wildcards, to avoid ambiguity. For example, here are the functions you’ve declared so far:

  • printGreeting
  • sayHello(person:)
  • greet(person:greeting:)
  • max(a:b:)
  • switchUser(user:)
  • sayHello(to:)
  • switchUser(to:)
  • greet(_:with:)
  • max(_:_:)

Overloading ​

Did you notice the previous list contains multiple functions with the same name?

  • sayHello(person:) and sayHello(to:)
  • greet(person:greeting:) and greet(_:with:)
  • max(a:b:) and max(_:_:)
  • switchUser(user:) and switchUser(to:)

Swift lets you declare multiple functions with the same name; this is known as overloading. This technique is helpful in declaring related functions, but you should use it sparingly to avoid confusion.

You may overload a function only when the compiler can unambiguously determine which function is being called. Therefore, overloaded functions must differ on at least one of the following points:

  1. The number of parameters.
  2. The external parameter names.
  3. The types of the parameters.
  4. The return type.

The functions listed above all differ in their external parameter names.

Here are a few more examples:

swift
func max(_ a: Int, _ b: Int, _ c: Int) -> Int {
  if a > b {
    return a > c ? a : c
  } else {
    return b > c ? b : c
  }
}

func max(_ a: Double, _ b: Double, _ c: Double) -> Double {
  if a > b {
    return a > c ? a : c
  } else {
    return b > c ? b : c
  }
}

Neither of these conflict with the already declared function max(_:_:), or with each other:

  • The first example has three parameters, whereas max(_:_:) only has two.
  • The second example has the same number of parameters and the same external parameter names as the first example, but it has different parameter types and a different return type.

Overloads that differ only in their return type are less common. The following functions are an example of this. Both round a number to the nearest integer. The first returns its result as an Int; the second as a Double:

swift
func rounded(_ x: Double) -> Int {
  let remainder = x.truncatingRemainder(dividingBy: 1)
  switch remainder {
  case _ where remainder <= -0.5:
    return Int(x - 1)
  case _ where remainder >= 0.5:
    return Int(x + 1)
  default:
    return Int(x)
  }
}

func rounded(_ x: Double) -> Double {
  let remainder = x.truncatingRemainder(dividingBy: 1)
  switch remainder {
  case _ where remainder <= -0.5:
    return Double(Int(x - 1))
  case _ where remainder >= 0.5:
    return Double(Int(x + 1))
  default:
    return Double(Int(x))
  }
}

Because these functions differ only in their return type, you need a type annotation to disambiguate between them:

swift
let roundedNumber: Double = rounded(-0.5)

Here, the compiler picks the version of rounded(_:) that returns a Double. Without the annotation, this code would be invalid.

Default parameters ​

A function can assign default values to its parameters:

swift
func greet(_ person: String, with greeting: String = "Hello") {
  print("\(greeting), \(person)")
}

A call to this function can either provide a value for greeting or use the default value "Hello":

swift
greet("Steven", with: "Hi")
greet("Steven")

The print function also uses default parameters. In Control Flow and Booleans, you used the following loop to print a number of dots:

swift
var dots = 3
while dots > 0 {
  print(".", terminator: "")
  dots -= 1
}
print()

The print function has a terminator parameter with a default value of \n. By setting this parameter to an empty string, you stop the function from adding a newline character so that you can print multiple strings on the same line. The final call to print prints an empty string with a newline terminator, so subsequent text appears on a new line.

Although Swift doesn’t require it, default parameters should be at the end of the parameter list. That way, they don’t affect the position of the required parameters.

Recursion ​

A function may call itself as part of its implementation. This technique is known as recursion.

Here’s a function that uses recursion to calculate the sum of the numbers 1 to n:

swift
func sumOfOneTo(_ n: Int) -> Int {
  if n == 1 {
    1
  } else {
    sumOfOneTo(n - 1) + n
  }
}

Note

This function has an implicit return statement that returns the result of an if expression.

Here’s how this recursion works:

  1. When n is 1, the function returns 1. This is the base case.
  2. For any n larger than 1, the function adds n to the sum of the numbers 1 to n - 1. This is the recursive case.

A recursive function must have a base case. Without it, the recursion won’t end until the program runs out of memory and crashes.

Recursion can be an elegant solution to many problems. However, it comes at a cost. To illustrate, here’s what happens when you call sumOfOneTo(3):

  1. sumOfOneTo is called with an argument of 3.
  2. sumOfOneTo(3) makes a recursive call to sumOfOneTo(2).
  3. sumOfOneTo(2) makes a recursive call to sumOfOneTo(1).
  4. sumOfOneTo(1) is the base case and returns 1.
  5. sumOfOneTo(2) adds 2 to this value and returns 3.
  6. sumOfOneTo(3) adds 3 to this value and returns 6.

After step 3, three function calls are waiting to complete. These calls are using memory that cannot be freed until the base case is reached, and the recursion starts to unwind.

A non-recursive implementation often performs better than a recursive one. For comparison, here’s an implementation of sumOfOneTo that doesn’t use recursion:

swift
func sumOfOneTo(_ n: Int) -> Int {
  var sum = 0
  for number in 1...n {
    sum += number
  }
  return sum
}

This implementation doesn’t require additional function calls and performs much better.

Nevertheless, recursion is an important technique to master.

Up next ​

Up next is another set of exercises. Work your way through these exercises, then continue on to your first challenge, where you’ll build a game of Tic-Tac-Toe.