Что такое mutating, и где он используется?


В контексте программирования термин mutating чаще всего ассоциируется с языком Swift, где он является специальным ключевым словом, применяемым к методам структур и перечислений. Основное его назначение — разрешить методам изменять свойства структуры или перечисления, к которым они принадлежат.

В других языках термин “mutating” также используется в общем смысле, как обозначение операций, изменяющих внутреннее состояние объекта или данных, но в Swift он закреплён в языке на уровне синтаксиса и компиляции.

1. Что значит "mutating" в Swift

Swift делает чёткое различие между изменяемыми (mutable) и неизменяемыми (immutable) структурами. По умолчанию все значимые типы (например, struct и enum) в Swift — неизменяемые. Это означает, что методы таких типов не могут менять значения их свойств без явного разрешения. Именно поэтому используется ключевое слово mutating.

2. Почему это нужно

В отличие от классов, структуры в Swift — значимые типы (value types). Когда вы вызываете метод на экземпляре структуры, метод работает с копией значения. Поэтому если метод должен изменить свойства структуры, это нужно явно разрешить через mutating.

Если не использовать mutating, компилятор не позволит изменять свойства.

3. Пример использования mutating

struct Point {
var x: Int
var y: Int
mutating func moveBy(x deltaX: Int, y deltaY: Int) {
x += deltaX
y += deltaY
}
}

Без ключевого слова mutating компилятор Swift выдаст ошибку:

Cannot assign to property: 'self' is immutable

Это означает, что метод, модифицирующий состояние структуры, должен быть явно объявлен как mutating.

4. Что может делать mutating метод

mutating метод может:

  • изменять значения переменных (var) свойств структуры;

  • вызывать другие mutating методы;

  • присваивать self новое значение;

  • изменять вложенные изменяемые структуры.

Пример:

struct Rectangle {
var width: Int
var height: Int
mutating func scale(by factor: Int) {
self = Rectangle(width: width \* factor, height: height \* factor)
}
}

Здесь self получает новое значение — такое присваивание также требует mutating.

5. Использование в перечислениях (enum)

В enum mutating используется, чтобы изменять текущее состояние перечисления, например:

enum SwitchState {
case on
case off
mutating func toggle() {
self = (self == .on) ? .off : .on
}
}

Без mutating Swift не позволит изменить self.

6. Работа с let и var переменными

Если экземпляр структуры объявлен с let, вы не можете вызывать на нём mutating методы:

var p = Point(x: 0, y: 0)
p.moveBy(x: 5, y: 3) // работает
let q = Point(x: 0, y: 0)
q.moveBy(x: 5, y: 3) // ошибка!

Хотя moveBy правильно помечен как mutating, q — это неизменяемая переменная, и её состояние нельзя изменить.

7. mutating и классы

Для классов (class) в Swift mutating не используется, так как классы — это ссылочные типы (reference types), и методы по умолчанию могут изменять свойства экземпляра:

class Counter {
var value = 0
func increment() {
value += 1 // работает без \`mutating\`
}
}

Таким образом, ключевое слово mutating актуально только для структур и перечислений.

8. mutating и протоколы

Если вы определяете метод в протоколе, который может быть реализован структурой или перечислением, и он должен быть изменяющим, вы тоже должны использовать mutating в протоколе:

protocol Resettable {
mutating func reset()
}
struct Timer: Resettable {
var time = 0
mutating func reset() {
time = 0
}
}

Без mutating в протоколе компилятор не позволит структурной реализации изменить состояние.

9. Связь с функциональным программированием

Swift, как и многие современные языки, стремится к более чистому функциональному стилю, где данные неизменяемы, а состояния контролируются явно. Ключевое слово mutating позволяет:

  • избежать неявных побочных эффектов;

  • повысить читаемость и предсказуемость кода;

  • сделать отличия между функциями, которые меняют объект, и теми, что не меняют.

10. Сравнение с другими языками

Язык Эквивалент mutating Поведение
Swift mutating Только для структур и enum
--- --- ---
Rust mut self в сигнатуре метода Изменение состояния объекта
--- --- ---
Kotlin Нет mutating, но val/var + copy Через копирование и data class
--- --- ---
Python Нет ключевого слова, но всё mutates Всё по ссылке
--- --- ---
JavaScript Нет mutating, но mutable объекты Всё по ссылке
--- --- ---
C++ const методы, иначе разрешена мутация Контролируется через квалификаторы
--- --- ---

Таким образом, Swift — один из немногих языков, где изменение состояния жёстко контролируется компилятором, что делает mutating мощным инструментом управления изменяемостью.

11. Пример: изменение значения через mutating и copy

Допустим, вы реализуете Vector2D:

struct Vector2D {
var x: Double
var y: Double
mutating func normalize() {
let length = (x \* x + y \* y).squareRoot()
x /= length
y /= length
}
}

Здесь normalize изменяет x и y текущего экземпляра — без mutating компилятор не позволит этого сделать.

12. Совместимость с методами без мутации

Иногда полезно предоставлять и мутационные, и немутационные варианты:

struct Vector {
var x: Double
var y: Double
mutating func scaleInPlace(by factor: Double) {
x \*= factor
y \*= factor
}
func scaled(by factor: Double) -> Vector {
return Vector(x: x \* factor, y: y \* factor)
}
}

Пользователь может выбрать:

  • v.scaleInPlace(by: 2) — изменить v;

  • let newV = v.scaled(by: 2) — оставить v нетронутым, получить новый.

Такой стиль широко используется в Swift API.

13. Общие принципы использования mutating

  • Используйте, если метод должен модифицировать экземпляр структуры или перечисления.

  • Всегда объявляйте такие методы с mutating, иначе компилятор выдаст ошибку.

  • Помните, что mutating можно вызывать только на переменных, объявленных с var, а не с let.

  • В протоколах — обязательно дублируйте mutating в сигнатуре, если метод предполагается как изменяющий.