KotlinのListとSwiftのArrayの違い

これは [Mobile Act OSAKA #1](https://mobileact.connpass.com/event/65935/) での発表した内容をまとめたものです。

---

iOS / Android 双方に関係あることとして、 Kotlin の `List` と Swift の `Array` を比べてみます。

## 一番大事なこと

最初に一番大事なことを言います。それは、 Kotlin の `List` は **参照型** だということです。コレクションが参照型なのは当たり前と思うかもしれませんが、 Swift の `Array` はなんと **値型** です。この違いがこれから説明するすべての違いを生む原因なので重要です。

## ミュータビリティ

では実際にどんな違いがあるのか、まずはミュータビリティについてです。

### Kotlin

Kotlin では `List` がミュータブルかイミュータブルかを型で表します。

```kotlin
val a: MutableList<Int> = mutableListOf(2, 3, 5)
a.add(7) // [2, 3, 5, 7]

val b: ImmutableList<Int> = immutableListOf(2, 3, 5)
val b2 = b.add(7) // b=[2, 3, 5], b2=[2, 3, 5, 7]

// `val` は Swift の `let` 相当
```

ミュータブルだとこのように新しい要素を `add` するとそのインスタンス自身に変更が加えられますが、イミュータブルだと当然インスタンスを変更することはできません。 `add` すると新しい要素が加えられた生成され返されます。

ちなみに、この `ImmutableList` はまだ [Proposal](https://github.com/Kotlin/kotlinx.collections.immutable/blob/master/proposal.md) の段階なので標準ライブラリには存在しません。将来的には追加されるんじゃないかと思います。

この二つのリストを抽象的にまとめて扱いたいことがあるので、第三の型として、これらのスーパータイプである `List` がほしくなります。

```
List <-+-- ImmutableList
       |
       +-- MutableList
```

`List` は `MutableList` や `ImmutableList` のスーパータイプなので、それらのインスタンスを `List` 型の変数に代入することができます。しかし、逆はできません。

```kotlin
val c: List<Int> = mutableListOf(2, 3, 5) // OK
val d: MutableList<Int> = c // NG
```

↓のように明示的に変換してやる必要があります。

```kotlin
val c: List<Int> = mutableListOf(2, 3, 5) // OK
// val d: MutableList<Int> = c // NG
val d: MutableList<Int> = c.toMutableList() // OK
```

`MutableList` と `ImmutableList` の間でも当然変換が必要です。

このように、 Kotlin ではミュータビリティを適切に扱おうとすると三つの型を使い分ける必要があります。

### Swift

では Swift ではどうかと言うと `Array` 型一つだけを使います。 `Array` は値型なので、ミュータビリティの切り替えは `var` か `let` か、つまり変数か定数かによって行います。

```swift
var a: Array<Int> = [2, 3, 5]
a.append(7) // OK

let b: Array<Int> = [2, 3, 5]
b.append(7) // NG

// `let` は Kotlin の `val` 相当
```

`var` で宣言すればミュータブルなので `append` で要素を追加することができますが、 `let` にするとイミュータブルなので `append` がコンパイルエラーになります。

また、 Kotlin の場合と違い、 `var` だろうと `let` だろうと型の上では同じ `Array<Int>` 型なので、相互に代入することができます。

```swift
let c: Array<Int> = [2, 3, 5] // OK
var d: Array<Int> = c // OK
```

## インスタンスの共有

次はインスタンスの共有についてです。

### Kotlin

Kotlin の `List` は参照型なので↓のように `a` を `b` に代入してから `a` を変更すると `b` も変更されてしまいます。

```kotlin
val a: MutableList<Int> = mutableListOf(2, 3, 5)
val b: List<Int> = a
a.add(7)
println(b) // [2, 3, 5, 7]
```

これは、 `a` と `b` が同一のインスタンスを参照しているからです。

意図せずにインスタンスが共有されてしまうとバグを生みかねません。特に、コンストラクタやメソッドの引数に渡す場合や、メソッドの戻り値として返す場合など、インスタンスをまたぐときに問題を生みやすいので注意が必要です。

それを避けるためには明示的にコピーをする必要があります。

```kotlin
val a: MutableList<Int> = mutableListOf(2, 3, 5)
val b: List<Int> = a.toList() // Copy
a.add(7)
println(b) // [2, 3, 5, 7]
```

### Swift

Swift では `Array` は値型なのでインスタンスが共有されることはありません。同じように、 `a` を `b` に代入してから `a` を変更しても `b` が変更されることはありません。

```swift
var a: Array<Int> = [2, 3, 5]
let b: Array<Int> = a
a.append(7)
print(b) // [2, 3, 5]
```

値型は代入の度にコピーされるので、コレクションのようにインスタンスが巨大だとコピーコストが気になりますが、 Swift の `Array` は Copy-on-Write という仕組みで無駄なコピーが発生しないようになっています。

たとえばこの例では代入のタイミングではなく `append` したときに Lazy にコピーされます。

```swift
var a: Array<Int> = [2, 3, 5]
let b: Array<Int> = a // No copy
a.append(7)
print(b) // [2, 3, 5]
```

これは参照型で先行してコピーコストを支払わなければならないのと対照的です。

## 変性

最後は変性（ Variance ）についてです。

### Kotlin

`Animal` クラスを継承した `Cat` クラスがあるとします。

`Cat` の `List` は `Animal` の `List` のサブタイプになります。そのため、↓の `cats` を `animals` に代入することができます。

```kotlin
val cats: List<Cat> = listOf(Cat())
val animals: List<Animal> = cats // OK
```

このような性質を、 `List` は型パラメータ `E` について共変（ Covariant ）であると言います。これは `ImmutableList` でも同じです。

しかし、 `MutableList` では事情が異なります。もし、この `cats` を `animals` に代入できてしまうと、↓のように `animals` を通して `cats` に `Dog` を追加でき、タイプセーフティが破綻してしまいます。なので `MutableList` は共変ではなく非変（ Invariant ）です。

```kotlin
val cats: MutableList<Cat> = mutableListOf(Cat())
val animals: MutableList<Animal> = cats // NG
animals.add(Dog()) // !!
```

### Swift

Swift はどうかというと、当然イミュータブルな `Array` は共変です。

```swift
let cats: Array<Cat> = [Cat()]
let animals: Array<Animal> = cats // OK
```

しかし、なんとミュータブルな `Array` でも `cats` を `animals` に代入可能です。

```swift
var cats: Array<Cat> = [Cat()]
var animals: Array<Animal> = cats // OK
```

これは、 Swift の `Array` が値型だからです。インスタンスが共有されることはないので、 `animals` に `Dog` を加えても `cats` は変更されません。

```swift
animals.append(Dog()) // OK
```

## まとめ

Kotlin の `List` と Swift の `Array` では挙動が色々と異なります。この違いを知らずに単純移植しようとすると大怪我をしかねないので気を付けて下さい。

個人的には、値型のコレクションはめずらしくて挙動がおもしろいので Swift の `Array` には注目しています。みなさんも興味があれば色々調べてみて下さい。