第 1 章  Value Semantics

Value Semantics とは

Value Semantics という用語は C++ などの言語で用いられることが多いようです。しかし、 Swift における Value Semantics は、それらとは少し異なるニュアンスを持っています。 WWDC 2015 のセッション “Building Better Apps with Value Types in Swift” の中で Swift における Value Semantics について詳しく説明されていますが、残念ながらその定義については述べられていません。 Swift における Value Semantics の定義は、 Swift のリポジトリの中のドキュメント “Value Semantics in Swift” に記載されています。

これは 2013 年に書かれた古いドキュメントで、 docs/proposals という正式に認められていないドキュメントが収められたディレクトリの中にありますが、 Swift Core Team の Dave Abrahams さんがその著者であり、内容も WWDC で話されていることと一貫性があるので、信頼のおけるドキュメントだと筆者は考えています。

このドキュメントの中で、 Value Semantics は次のように定義されています。

For a type with value semantics, variable initialization, assignment, and argument-passing each create an independently modifiable copy of the source value that is interchangeable with the source.

(参考訳)ある型が Value Semantics を持っているとき、その型の変数を初期化したり、値を代入したり、引数に渡したりすると、元の値のコピーが作られて、そのコピーと元の値は独立に変更することができる。つまり、どちらかを変更してももう片方には影響を与えない。

“Value Semantics in Swift”

コードを使って例を挙げてみます。

Value Semantics を持つ例

value という Stored Property を一つだけ持つシンプルな structFoo を考えてみます。

struct Foo {
    var value: Int = 0
}

この Foo を使って次のような処理を考えます。

var a = Foo()
var b = a
a.value = 2

このときに、 b.value も変更されるかというのが問題です。結論から言うと、 Foo値型 なので a.value を変更しても b.value は変更されません。

値型 のインスタンスは変数に直接格納されています。インスタンスを表すバイト列が、メモリ上のその変数を表す領域に直接書かれているわけです。 値型 のインスタンスを変数に代入したときには、そのインスタンスを表すバイト列がコピーされて代入先の変数の領域にそのまま書き込まれます。これが 値型 のインスタンスがコピーされる仕組みです。

上記の例では、 var b = a をした時点で a の領域に格納されたバイト列が b の領域にコピーされるわけです。代入直後の時点では、 ab には同じ内容のバイト列が書かれていますが、それらは内容が同じ別々の Foo インスタンスを表しています。 a.value を変更しても a の領域の value のバイト列が変更されるだけで、 b.value が変更されることはありません。

シンプルな値型と状態の変更

今、 ab は変更に対して独立である、つまり、 ab のどちらかに変更を加えてももう一方には影響を及ぼさないので、 FooValue Semantics を持っていると言えます。

Value Semantics を持たない例

次に、 Value Semantics を持たない例を見てみます。先程のコードの struct だった部分を class に変更します。それ以外はまったく同じです。

class Foo {
    var value: Int = 0
}

var a = Foo()
var b = a
a.value = 2

この場合は、 Foo はクラスなので 参照型 です。そのため、 a.value を変更することで b.value も変更されてしまいます。

参照型 のインスタンスは変数に直接格納されません。インスタンスの実体を表すバイト列はメモリ上の別の領域(ヒープ領域のどこか)に格納されていて、その領域を表すメモリのアドレスが変数に格納されます。たとえば、そのアドレスが 0x123DEF だとすると、変数に実際に格納されているのは 0x123DEF というアドレスを表すバイト列です。 var b = a では、 a に格納されたアドレス 0x123DEF を表すバイト列が b にコピーされます。このとき、 ab は同じアドレス 0x123DEF を介して同一の Foo インスタンスを参照することになります。そのため、 a.value を変更すると b.value も変更されてしまうわけです。

シンプルな参照型と状態の変更

この例では変更に対する独立性を持たないので、 FooValue Semantics を持ちません。

このように、片一方を変更するともう一方も変更される場合、 Value Semantics と対比して、その型は Reference Semantics を持っていると言われます。

Semantics vs Type

先の例や名前からも推測できる通り、 Value Semantics値型( Value Type ) と、 Reference Semantics参照型( Reference Type ) と深い関係があります。しかし、これらは同じものではありません。混同してしまわないように注意が必要です。

たとえば、 値型 だけど Value Semantics を持たない型 や、 参照型 だけど Reference Semantics を持つ型も存在しますValue SemanticsReference Semantics と、 値型参照型 をきちんと区別して考えることが重要です。

値型だけど Value Semantics を持たない例

Semantics と Type を区別して考えるために、値型だけれども Value Semantics を持たない例を見てみましょう。

struct Foo に加えて、 Bar というクラスを導入します。

class Bar {
    var value: Int = 0
}

この Bar 型のプロパティを Foo に追加します。

struct Foo {
    var value: Int = 0
    var bar: Bar = Bar() // 👈
}

これを使って先程と似たようなことをしてみます。ただし、 a.value を変更するのに加えて a.bar.value も変更します。

var a = Foo()
var b = a
a.value = 2
a.bar.value = 3 // 👈

このとき、 Foo値型 なので変数 a, b には独立した別々の Foo インスタンスが格納されます。しかし、 Bar参照型 なので、 abbar プロパティには同じ Bar インスタンスのアドレスが格納され、そのアドレスを介して同じインスタンスを参照していることになります。

その状態で a.value を変更しても、 ab には異なる Foo インスタンスが格納されているので b.value には影響を与えません。しかし、 a.barb.bar は同じ Bar インスタンスを参照しているので、 a.bar.value に変更を加えると b.bar.value も変更されます。

参照型を保持する値型と状態の変更

そのため、 Foo値型 であるにも関わらず変更に対する独立性を持たない、つまり Value Semantics を持たないことになります。なお、 a.valueb.value は独立に変更できるので、この FooReference Type も持ちません。

この Foo のように Value SemanticsReference Semantics も持たない型は扱いづらく、そのような型を作ってしまわないように注意が必要です。そんな変な型を作ることはないと思うかもしれませんが、たとえば下記に挙げたようなクラスのインスタンスを 値型 のプロパティに持たせると、簡単に Value SemanticsReference Semantics も持たない型ができあがってしまいます。

参照型プロパティを持つ値型でも Value Semantics を持つ例

先の例は、一見 参照型 のプロパティを持ったことが Value Semantics を失った原因のように思えます。しかし、 参照型 のプロパティを持つことで必ずしも Value Semantics が失われるわけではありません。次は、 参照型 のプロパティを持つ 値型Value Semantics を持つ例を見てみます。

先のコードの Bar クラスを イミュータブルクラス に変更します。

final class Bar {
    let value: Int = 0
}

Bar クラスを イミュータブル にするために、 Barvalue プロパティを let にして、 final class に変更します。 final class にするのは、 Barミュータブル なサブクラスが作られてしまうと Bar 型の イミュータビリティ が破壊されてしまうからです。

この FooBar を使って先程と同じことをしてみます。そうすると、 Bar は今 イミュータブルクラス なので、 a.bar.value を変更しようとすると当然コンパイルエラーになります。

var a = Foo()
var b = a
a.value = 2
a.bar.value = 3 // ⛔

このケースでは、たしかに a.barb.bar は同じインスタンスを参照していますが、 Barイミュータブル なので、そのインスタンスを通して状態を変更することはできません。そのため、 Bar 型のプロパティを持つことが Foo インスタンスの変更に対する独立性を破壊することにはつながりません。結果として、 Foo参照型 のプロパティを持つにも関わらず Value Semantics を持つということになります。

このようなケースはよく見られ、たとえば次のようなクラスのインスタンスをプロパティに保持しても Value Semantics を破壊する原因にはなりません。

イミュータビリティと Semantics

イミュータブルクラス 自体の Semantics はどのように考えれば良いでしょうか。

次のような イミュータブルクラス Foo を考えます。

final class Foo {
    let value: Int = 0
}

この Foo に対して、同様の処理を行います。

var a = Foo()
var b = a
a.value = 2 // ⛔

イミュータブルクラス のインスタンスはそもそも変更することができないので、片方を変更するともう片方も変更されるということは起こりません。 “Value Semantics in Swift” には、そのようなケースも Value Semantics を持つと書かれています。

また、 “Value Semantics in Swift” には興味深いことが書かれていて、 イミュータビリティ を持つ場合は Value SemanticsReference Semantics が区別できないとあります。そのため、上記のような イミュータブルFoo クラスは Value SemanticsReference Semantics を両方持つと言えます。また、 イミュータブル であれば、 値型 であっても Reference Semantics を持つと言えるでしょう。たとえば、次の Foo値型 ですが Value SemanticsReference Semantics の両方を持っています。

struct Foo {
    let value: Int = 0
}

ミュータブルな参照型をプロパティに持つけど Value Semantics を持つ例

先程の例では、 ミュータブル参照型 のプロパティを持つ場合は Value Semantics を持ちませんでした。

struct Foo {
    var value: Int = 0
    var bar: Bar = Bar()
}

class Bar {
    var value: Int = 0
}

しかし、「 ミュータブル参照型 のプロパティを持つ場合は Value Semantics を持たない」というようにパターンで判断するのは危険です。たとえば、標準ライブラリの Array は内部に ミュータブル参照型 を保持していますが、 Copy-on-Write という仕組みを使って Value Semantics を実現しています。

Value Semantics を持つかどうかはパターンで判断するのではなく、定義に基づいて判断することが大切です。

まとめ

Swift における Value Semantics の定義は、ある型が Value Semantics を持つとき、その型の値が変更に対して独立であるということです。

値型Value Semantics参照型Reference Semantics は同じものではなく、 値型 だからといって Value Semantics を持つとは限りませんし、 参照型 でも Value Semantics を持つこともあります。 Type と Semantics を区別して理解することが重要です。 Value Semantics を持つかどうかをパターンに当てはめて考えると、様々な例外を考慮しなければなりません。 Value Semantics を持つかどうかは定義に基づいて判断しましょう。