Перейти к содержимому
Теория · Функции · урок 5 из 9

Параметры и inout

Настоящий комбайн перемалывает ваше яблоко, и на столе его больше нет. Комбайн-функция устроен иначе: в отверстие попадает копия, а яблоко на столе остаётся целым. Так Swift передаёт значения в функцию:

SWIFT
func withBonus(_ score: Int) -> Int {
    score + 10
}

var points = 50
let shown = withBonus(points)
print(points, shown) // 50 60

Давайте разберём, что происходит:

  1. withBonus(points) передаёт в функцию копию числа 50.
  2. Функция прибавляет к копии 10 и возвращает 60, а это число попадает в shown.
  3. Сама переменная points не изменилась, и программа печатает 50 60.

Параметр — копия

Параметр получает копию значения аргумента. Что бы функция ни делала со своей копией, переменная снаружи не изменится. Менять параметр внутри тела нельзя — он константа let:

SWIFT
func addBonus(_ score: Int) {
    score = score + 10 // ✗
}
TEXT
error: cannot assign to value: 'score' is a 'let' constant

Это то же сообщение, что и при присваивании обычной константе. Так Swift не даёт изменить параметр по ошибке: внутри тела он всегда равен тому, что передали при вызове. Наружу из функции выходит только результат.

Параметр inout

Иногда функция как раз должна изменить переменную того, кто её вызвал. В первом уроке темы функция не могла тронуть переменную var из главной части программы. Это можно сделать явно: передать переменную при вызове, чтобы функция записала в неё новое значение. Для этого параметр помечают словом inout, а перед аргументом ставят &:

SWIFT
func addBonus(_ score: inout Int) {
    score += 10
}

addBonus(&points)
print(points) // 60
addBonus(&points)
print(points) // 70

Разберём, что здесь нового:

  • inout перед типом разрешает функции менять параметр, и изменение вернётся в переменную снаружи;
  • & перед аргументом обязателен: по нему при чтении вызова видно, что points может измениться;
  • после каждого вызова points больше на 10, поэтому программа печатает 60, а потом 70.

Такой параметр называют сквозным: значение проходит через функцию и возвращается в ту же переменную. В разговоре и на собеседованиях его чаще называют inout-параметром.

Аргументом для inout может быть только переменная var:

  • константу let передать нельзя: после let base = 50 вызов addBonus(&base) не скомпилируется с ошибкой cannot pass immutable value as inout argument: 'base' is a 'let' constant;
  • число, записанное прямо в коде, тоже нельзя: addBonus(&5) не скомпилируется.

Копия туда и обратно

Сквозной параметр работает по правилу «копия на входе, копия на выходе»:

  1. При вызове значение points копируется в параметр score.
  2. Функция меняет свою копию.
  3. Когда функция завершается, значение копии записывается обратно в points.

Этот вопрос часто задают на собеседовании: передаётся ли inout по ссылке, то есть работает ли функция с самой переменной, а не с копией? По правилам языка — нет: значение копируется туда и обратно. Компилятор может ускорить это и работать прямо с исходной переменной, но программа обязана вести себя так, как при копировании.

Частая ошибка

При вызове забыли &:

SWIFT
addBonus(points) // ✗
TEXT
error: passing value of type 'Int' to an inout parameter requires explicit '&'

Программа не компилируется. Swift требует & у каждого аргумента для inout, чтобы изменение переменной было видно в самом вызове. Без этого значка читатель кода решил бы, что после вызова points осталась прежней. Допишите &: addBonus(&points).

Главное

  • Параметр получает копию аргумента и внутри тела остаётся константой: менять его нельзя, и переменная снаружи не меняется.
  • inout перед типом позволяет функции изменить переменную того, кто её вызвал; перед аргументом при вызове ставят &.
  • Сквозной параметр работает копированием: значение копируется в функцию и записывается обратно, когда она завершается.
  • Аргументом для inout может быть только переменная var, но не константа и не число в коде.