Перейти к содержимому
Теория · Структуры · урок 2 из 9

Методы и mutating

Вернёмся к бланку Point. Внизу на нём напечатаны две инструкции: «как записать точку в скобках» и «как сдвинуть точку вправо на несколько шагов». Каждая инструкция работает с графами того бланка, на котором она напечатана.

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

SWIFT
struct Point {
    var x: Int
    var y: Int

    func describe() -> String {
        "(\(x), \(y))"
    }

    mutating func moveRight(by step: Int) {
        x += step
    }
}

var p = Point(x: 1, y: 2)
print(p.describe()) // (1, 2)

Давайте разберём программу по шагам:

  1. Внутри struct Point, рядом со свойствами, объявлены два метода: describe и moveRight.
  2. var p = Point(x: 1, y: 2) создаёт экземпляр.
  3. p.describe() вызывает метод через точку, у конкретного экземпляра p. Метод собирает строку из его свойств и возвращает (1, 2).

Слово mutating перед вторым методом разберём чуть ниже.

Метод структуры

Метод у структуры такой же, как у перечислений: это функция внутри фигурных скобок типа. Его вызывают через точку у экземпляра, p.describe(). Всё, что вы знаете о функциях, работает и здесь: параметры, метки аргументов, результат после ->.

Главное отличие от обычной функции: метод видит свойства своего экземпляра. Внутри describe стоят только имена x и y: это свойства того экземпляра, у которого вызвали метод. Для p это 1 и 2, а у другой точки будут её собственные числа.

Слово self

Внутри метода self — это экземпляр, у которого метод вызвали. Запись x в теле метода — сокращение от self.x.

Обычно self не пишут. Он нужен, когда у параметра то же имя, что у свойства:

  • в методе с параметром x запись x означает параметр;
  • self.x в том же методе означает свойство.

Метод, который меняет свойство

Здесь картинка с инструкциями неточна. На бумаге по любой инструкции можно исправить графу, а в Swift метод по умолчанию свойства только читает. Метод moveRight меняет свойство x, поэтому перед его func стоит mutating. Вызовем его:

SWIFT
p.moveRight(by: 9)
print(p.describe()) // (10, 2)

После вызова в p.x лежит 10, а y не изменился.

Если убрать mutating, программа не скомпилируется:

TEXT
error: left side of mutating operator isn't mutable: 'self' is immutable

Компилятор говорит: левую часть += менять нельзя, потому что self неизменяем. С обычным присваиванием x = x + step сообщение звучит иначе: cannot assign to property: 'self' is immutable. Суть в обоих одна — 'self' is immutable.

Почему так? Вспомните параметры функции: параметр — константа, и менять его в теле нельзя. Внутри метода self — такая же константа. Слово mutating разрешает методу менять self и работает как inout. Метод получает экземпляр, меняет его, а при выходе изменения записываются обратно в p.

Метод без mutating свойства читает, но не меняет, как describe. Поэтому mutating ставят только тем методам, которые меняют свойства.

Этот вопрос часто задают на собеседовании: зачем методу структуры mutating и почему его нельзя вызвать у let? Ответ состоит из двух частей:

  • внутри метода self — константа, поэтому метод, который меняет свойство, помечают mutating, и он получает self как сквозной параметр;
  • экземпляр в let не может меняться вовсе, поэтому вызов mutating-метода у него не компилируется.

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

mutating-метод вызвали у экземпляра в let:

SWIFT
let home = Point(x: 0, y: 0)
home.moveRight(by: 1) // ✗
TEXT
error: cannot use mutating member on immutable value: 'home' is a 'let' constant

Программа не компилируется, и не выполняется ни одна строка. Swift не делает втихую копию home, чтобы сдвинуть её: экземпляр в let — запечатанный бланк, а moveRight исправляет на нём графу. Как и параметру inout, mutating-методу нужна переменная. Объявите экземпляр через var, и вызов заработает.

Главное

  • Метод — функция внутри типа; его вызывают через точку у экземпляра, и он видит свойства этого экземпляра.
  • self — экземпляр, у которого вызвали метод; self.x пишут, когда у параметра то же имя x.
  • Метод, который меняет свойство, помечают mutating: без него self внутри метода — константа.
  • mutating-метод нельзя вызвать у экземпляра в let: такая программа не компилируется.