When coding in Swift, we usually don’t need to mutate the arguments that we provide to functions, and then make these changes visible back to the call site. We mostly use them as inputs to calculations or other expressions, and if necessary, we return some value from functions. But even if we tried to modify the parameter values, that would trigger a compile-time error, as parameter values are treated as constants, and therefore, they can’t be mutated.
To make my point about what I’m actually talking about, think of the following function that doubles the given number:
1 2 3 4 5 6 |
func double(number: Int) -> Int { let doubled = number * 2 return doubled } |
This simplistic example will work just fine, as it takes the parameter value as an input, it doubles it, it assigns the new value to another constant declared in the function, and then returns that value back to the caller. The next code though is not going to work:
1 2 3 4 5 |
func double(number: Int) { number *= 2 } |
Xcode will complain with the following error:
– Left side of mutating operator isn’t mutable: ‘number’ is a ‘let’ constant –
That says what I mentioned at the beginning; parameter values are considered to be constants, and they cannot be modified like variables can.
But, what if we really want to mutate the original parameter value? It might be sometimes much more suitable to do that, instead of returning a value from the function. Thankfully, we can, using in-out parameter values!
Mutating a parameter value
There is a certain way to let a function know that a parameter value should be treated as a variable and not as a constant. That is to use the inout
keyword right before the parameter’s data type.
Say, for example, that we are implementing a function to calculate the factorial number of 10 (10!). Instead of returning the factorial from the method though, we’ll update the parameter value. See how the inout
is used right next:
1 2 3 4 5 6 7 |
func factorial(result: inout Int) { for i in 1…10 { result *= i } } |
This will do its job perfectly, and the value that will be given as argument to factorial(result:)
will eventually get updated with the calculated result value. But marking the parameter with the inout
keyword is not the only requirement; upon calling the function we must prefix the variable’s name that we provide as argument with the ampersand (&) symbol.
In practice, we can call the factorial(result:)
function like so:
1 2 3 4 |
var number = 1 factorial(result: &number) |
See the ampersand symbol right before the number
argument, but also note that number
is a variable (declared with var
) and not a constant. Only variables can be provided as arguments to inout
parameters.
You can try the above in an Xcode playground, and to verify that number
gets changed after having called the factorial(result:)
, just print its value:
1 2 3 4 5 |
print(number) // It prints: 3628800 |
In-Out parameters can co-exist with normal parameters
It might be sometimes necessary to have both in-out parameters and normal parameters together in a function. This is allowed to do without having to deal with any kind of restrictions that limit the number of parameters or their order.
Suppose that we want to update the factorial(result:)
function, and make it possible to receive as argument the number that we want to calculate the factorial for:
1 2 3 4 5 6 7 8 9 10 11 |
func factorial(number: Int, result: inout Int) {| if number == 0 { result = 1 } else { for i in 1…number { result *= i } } } |
That new version of the factorial method now has two parameter values, with the first being a normal one, and the second being the in-out result. Also, the function’s body has been updated, as the zero number is a bit special.
We can now provide the number that we want to calculate the factorial for to factorial(number:result:)
function as argument:
1 2 3 4 5 6 7 8 |
var factorialNumber = 1 factorial(number: 7, result: &factorialNumber) print(factorialNumber) // It prints: 5040 |
Multiple In-Out parameters can exist in a function
The previous examples contained just one inout
parameter in the demonstrated function. However, we can have as many of them as necessary, and the following example shows that. The next function swaps two String values using in-out parameters:
1 2 3 4 5 6 7 |
func stringSwap(value1: inout String, value2: inout String) { let temp = value1 value1 = value2 value2 = temp } |
Swapping is a quite standard technique and does not require any further explanation. To make use of the above, we’ll initialize two String variables which we’ll pass as arguments prefixed with ampersand symbol:
1 2 3 4 5 6 7 8 9 |
var this = “this” var that = “that” stringSwap(value1: &this, value2: &that) print(“this = \(this), that = \(that)“) // It prints: // this = that, that = this |
Printing this
and that
after having swapped them verifies that their values have changed as expected.
Conclusion
In-out parameters serve a specific purpose, and that is to mutate the original values supplied as arguments to functions or methods. Unless it’s a desired effect, think twice before using them; mutability in multiple places can be the reason of potential unwanted troubles and bugs in code. Working with immutable values as much as possible, and modify only those that are really necessary where it’s actually necessary, can help avoid this kind of problems. However, do not hesitate to use in-out parameters if they’re better suited to what you’re coding, as they are a great programming tool if used properly; and now you know how.
Thank you for reading, take care! ????