If you have played with Apple's Combine framework, you might have seen something like this.

NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
    .sink { [weak self] _ in
        self?.saveNotes()
    }.store(in: &subscriptions)

Notice the ampersand (&) before the subscriptions variable. The ampersand indicates that the subscriptions variable is passed to the store(in:) method as an in-out parameter. What are in-out parameters? That is the focus of this episode.

Constant Parameters

Let's use a simpler example to explore in-out parameters. Take a look at this example. This isn't valid Swift. The compiler throws an error. Can you guess why?

var books: [String] = []

func addTitle(_ title: String, to books: [String]) {
    books.append(title)
}

addTitle("The Swift Programming Language", to: books)

The complier throws an error, telling us that books cannot be mutated. Why is that?

What are in-out parameters in Swift?

The parameters of a function or a method are constants by default in Swift. This is a good thing because a function or a method should cause as little side effects as possible.

What is a side effect? If a function or a method changes state outside of the scope of its function body we say that the function has side effects. This is something you need to avoid if possible.

Side effect aren't bad, though. They are inevitable if your goal is building software people can use. There are scenarios in which you want a function to change state outside of its body. The example we started with is a fine example. In the example, an AnyCancellable instance adds itself to a set of AnyCancellable instances.

How to Use In-Out Parameters

The parameters of a function are constants by default, but we can change this. We can use in-out parameters instead. Let's apply this pattern in the example I showed you earlier. To indicate that a parameter is an in-out parameter, we prefix the type of the parameter with the inout keyword.

var books: [String] = []

func addTitle(_ title: String, to books: inout [String]) {
    books.append(title)
}

addTitle("The Swift Programming Language", to: books)

The compiler no longer complains that books is mutated, but it throws another error.

In-Out Parameters in Swift?

The compiler asks us to explicitly indicate that books is passed as an argument to an inout parameter. We do this by prefixing the variable name with an ampersand (&).

var books: [String] = []

func addTitle(_ title: String, to books: inout [String]) {
    books.append(title)
}

addTitle("The Swift Programming Language", to: &books)

The example shows that we need to be explicit at the call site that the second parameter of the addTitle(_:to:) function is an in-out parameter. This is important to show anyone reading the code that books can be modified by the addTitle(_:to:) function.

Only Variables

Because a variable that is passed as an argument to an in-out parameter can be modified, it shouldn't surprise you when I say that only variables can be passed as arguments to in-out parameters. A constant cannot be modified so it makes no sense to pass a constant as an argument to an in-out parameter.

Only variables can be passed as arguments to an in-out parameter.

How Does It Work?

How do in-out parameters work in Swift? Let's use the example we explored earlier. When we call addTitle(_:to:), the value of books is copied and it is the copied value the function uses in its body. When the addTitle(_:to:) function returns, the copied value is assigned to the argument that was passed to the function. This is also known as copy-in copy-out. Swift makes some optimizations under the hood, but that is beyond the scope of this post.