Swift の class の mutating func とは何か

# 導入

Swift には struct と class があります。 struct のメソッドには `func` ( `nonmutating func` の省略表記 ) と `mutating func` がありますが、 class には `func` しかありません。実は、 protocol を使うと class においても `mutating func` を定義することができます。この記事ではこの class における `mutating func` が何を意味するのか解説します。

## バージョン

```
$ swift --version
Apple Swift version 4.0 (swiftlang-900.0.59 clang-900.0.34.2)
Target: x86_64-apple-macosx10.9
```

# 結論

`mutating func` が `self` の `inout` 渡しであること、 class 型の var が参照の可変性を表すことから、メソッド呼び出しのレシーバの変数の参照の可変性を意味するとわかる。

# class に `mutating func` を定義する

class に `mutating func` を書いても次のようなコンパイルエラーになります。

```
error: 'mutating' isn't valid on methods in classes or class-bound protocols
```

一方、 protocol を定義するときにはメソッド制約として `mutating func` を定義できます。そして struct がそれを実装するときには `func` か `mutating func` のどちらかを使えます。しかし class は `mutating func` が定義できないので、 `func` で実装することになります。実はここで  protocol のデフォルト実装を使うことで、 `mutating func` なメソッドをクラスに持たせることが可能です。

下記は `mutating func` のデフォルト実装を与えた例です。 protocol P0 には変更した値を返す版の `appended` と自己変更版の `append` があり、 `append` のデフォルト実装を、 `appended` を用いて書いています。`mutating func` の実装なので、 `self` を左辺に置いた代入文を書くことができます。これは class のメソッドではできないことですが、ここでは protocol なので可能です。

```swift
protocol P0 {
    func appended(_ x: Int) -> Self
    mutating func append(_ x: Int)
}

extension P0 {
    mutating func append(_ x: Int) {
        self = appended(x)
    }
}
```

この protocol を適当な class で conform します。 class 名は Cat にしました。デフォルト実装があるので、 `appended` メソッドだけ実装することで、 `mutating func` な `append` メソッドを持った class を作ることができます。

```swift
final class Cat : P0 {
	var age: Int = 1

	func appended(_ x: Int) -> Cat {
		let cat = Cat()
		cat.age += x
		return cat
	}
}
```

# class の `mutating func` を呼び出す

さて、この `class` のインスタンスに対してこれらのメソッドを呼び出そうとして、以下のようなコードを書いてもコンパイルエラーになってしまいます。

```swift
func main() {
	let cat = Cat()
	cat.append(1) // compile error
}
```

エラーはこれです。

```
error: cannot use mutating member on immutable value: 'cat' is a 'let' constant
```

`let` の mutating member が使えないと言われています。そこで `let` を `var` に書き換えると動くようになります。ついでに、メソッド呼び出しの前後で、 `cat.age` の値と、 `cat` の `ObjectIdentifier` を表示しておきます。

```swift
func main() {
    var cat = Cat()
    print(ObjectIdentifier(cat), cat.age)
    cat.append(1)
    print(ObjectIdentifier(cat), cat.age)
}
```

出力はこれです。

```
ObjectIdentifier(0x000060c000025280) 0
ObjectIdentifier(0x000060c000024f80) 1
```

`age` は期待通り `0` から `1` になっていますが、 `ObjectIdentifier` の値も変化しています。つまり、 **メソッド呼び出しによって、メソッドのレシーバの変数が別のインスタンスを参照するように変化した** ことがわかります。これが  `class` における `mutating func` の意味です。

# `mutating func` は `self` を `inout` 渡しする

上記の挙動は一見不思議ですが、 関連する概念を整理するとスッキリします。

ポイントは `mutating func` は `self` を `inout` 渡しするということです。([Ownership Manifesto#Function Parameters](https://github.com/apple/swift/blob/fa2f2bd4577e536130f9298fabfd2960a357693c/docs/OwnershipManifesto.md#function-parameters))

インスタンスメソッドというのは、レシーバのインスタンスも引数の一つと考えると、通常の関数としてとらえることができます。例えば、先程でてきた `nonmutating func` である `appended` メソッドは以下のように考えることができます。[^1] `self` を関数の引数として渡しています。

```swift
func P0_appended<X: P0>(_ self: X, _ x: Int) -> X { ... }
```

これが `mutating func` の場合は `self` が `inout` 渡しになります。 `inout` 渡しというのは、引数の型に `inout` が付くということです。 つまり、 `self` の型が `inout X` になります。 `append` を例にするとこうです。

```swift
func P0_append<X: P0>(_ self: inout X, _ x: Int) { ... }
```

この形式で class の `mutating func` を捉え直すことでわかりやすくなります。先程の `Cat` クラスにおける `append` メソッドは以下の関数のようにとらえられます。

```swift
func Cat_append(_ self: inout Cat, _ x: Int) { ... }
```

そして、 以下の `cat` に対するメソッド呼び出しは、次の関数呼び出しとしてとらえられます。

```swift
// メソッド呼び出し形式
cat.append(1)

// 関数呼び出し形式
Cat_append(&cat, 1)
```

後者の形式を見ると、 `cat` が `var` でなければならないこと、そして、関数呼び出しによって、 `cat` が指し示す値が、別のインスタンスに変わるかもしれないことがわかります。これこそが、 class の `mutating func` の意味となります。



[^1]: 本当は `nonmutating func` の `self` は `shared` 渡しのため不正確な記述ですが、現状の Swift に明示的な `shared` 渡しが存在しないのでこうしました。