INOUT parameters in Swift

Function parameters are constants by default. Means if you try to change the value of a function parameter from within the body of that function results in a compile-time error.

func foo(count: Int) {
    count = 5
}

Error: “error: cannot assign to value: 'count' is a 'let' constant”

This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.

Place the keywordinout right before a parameter’s type, to define it as a inout parameter.

var myInteger: Int = 0
func foo(count: inout Int) {
    count = 5
}
print(myInteger) // 0
foo(&myInteger)
print(myInteger) // 5

Output:

0
5
  • You can only pass a variable as the argument for an in-out parameter.
  • You cannot pass a constant or a literal value as the argument.
  • You place an ampersand (&) directly before a variable’s name when you pass it as an argument to an in-out parameter, to indicate that it can be modified by the function.
  • In-out parameters cannot have default values, and variadic parameters cannot be marked as inout.

How does inout work?

  1. When the function is called, the value of the argument is copied.
  2. In the body of the function, the copy is modified.
  3. When the function returns, the copy’s value is assigned to the original argument.

This behavior is known as copy-in copy-out or call by value result.

The “&” that you use at the front of an inout argument in Swift might give you the impression that inout parameters are essentially pass-by-reference. But they aren’t. inout is pass-by-value-and-copy-back, not pass-by-reference.

When a computed property or a property with observers is passed as an in-out parameter, its getter is called as part of the function call and its setter is called as part of the function return.

Have a look at below examples,

Example 1:

var myInteger: Int {
    get {
        print("Get")
        return 0
    }
    set {
        print("Set")
    }
}

func foo(count: inout Int) {
}

foo(count: &myInteger) 

Output:

Get
Set
Get

Example 2:

var myInteger: Int {
    get {
        print("Get")
        return 0
    }
    set {
        print("Set")
    }
}

func foo(count:  Int) {

}

foo(count: myInteger)

Output:

Get

Within a function, don’t access a value that was passed as an in-out argument, even if the original value is available in the current scope. Accessing the original is a simultaneous access of the value, which violates Swift’s memory exclusivity guarantee. For the same reason, you can’t pass the same value to multiple in-out parameters.

Using inout for an asynchronous task?

  • Using an inout parameter exclusively for an asynchronous task is an abuse of inout – as when calling the function, the caller’s value that is passed into the inout parameter will not be changed.
  • This is because inout isn’t a pass-by-reference, it’s just a mutable shadow copy of the parameter that’s written back to the caller when the function exits – and because an asynchronous function exits immediately, no changes will be written back.

In Swift 3, inout parameters are no longer allowed to be captured by @escaping closures, which eliminates the confusion of expecting a pass-by-reference.

var myInteger: Int = 0

func foo(count: inout Int) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
        count = 20
    }
}

foo(count: &myInteger)

Error: "error: Escaping closures can only capture inout parameters explicitly by value"

If you need to capture an in-out parameter for mutating it or to observe changes made by other code, use a capture list to explicitly capture the parameter immutably.

var myInteger: Int = 0

func foo(count: inout Int, completion: @escaping ()-> Void) {
     DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [count] in // capture list
        var count = count // mutable copy
        count = 20
        completion()
     }
}

foo(count: &myInteger) {
    print(self.myInteger)
}

Output:

0

In Above example, variable `myInteger` will not be affected, because we’re mutating the variable which is a copy of the count variable.

var myInteger: Int = 0

func foo(count: inout Int, completion: @escaping (Int)-> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [count] in
        var count = count
        count = 20
        completion(count)
    }
}

foo(count: &myInteger) { count in
    print(count)
}

Output:

20

The biggest advantage is that using inout is much safer than using references. 

As an optimization, when the argument is a value stored at a physical address in memory, the same memory location is used both inside and outside the function body. The optimized behavior is known as call by reference; which can be seen by looking at the SIL or IR emitted. However, you are unable to treat them as such due to the fact that there’s no guarantee whatsoever that the caller’s value will remain valid after the function call.
it satisfies all of the requirements of the copy-in copy-out model while removing the overhead of copying. Write your code using the model given by copy-in copy-out, without depending on the call-by-reference optimization, so that it behaves correctly with or without the optimization.