Что такое 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 в сигнатуре, если метод предполагается как изменяющий.