開発
D言語のimmutableとconstの違い
mima
こんにちは。
D言語には記憶クラスとしてimmutableとconstがあます。
どちらも似たような感じなのですが、実際には色々と違って複雑です。
最近、その差が少し分かってきたので、紹介します。
まず、immutableですが、これは読み取り専用の領域に置いてもいいことを意味します。
immutable int x = 2; x = 3; // コンパイルエラー immutable(int)[] y = [1, 2, 3].idup; y[0] = 3; // コンパイルエラー y = [3, 2, 3].idup; // OK
とてもC++のconstに似ています。
次に、constですが、これはimmutableか普通かのどちらかであることを意味します。
主にポインタや配列で使われます。
immutableである可能性があるため、immutableと同じように動作します。
void f(const(int)[] ary){ ary = [1, 2, 3].idup; ary[0] = 1; // コンパイルエラー } void main(){ int[] a = [1, 2, 3]; immutable(int)[] b = a.idup; f(a); f(b); }
コード内で、intとimmutable(int)はconst(int)に暗黙的にキャストされています。
一見要らないように見えるimmutableですが、利点があります。
関数の引数にconstをつけると、呼び出した側の変数がconstかそうでないかが分かりません。
呼び出す側もconstであって欲しい時があります。そういう時にimmutableが使えます。
void f1(const(int)[] ary){ ~~~ } void f2(immutable(int)[] ary){ ~~~ } void main(){ int[] a = [1, 2, 3]; const(int)[] b = a.idup; f1(a); // OK f1(b); // OK immutable(int)[] c = a.idup; f2(a); // コンパイルエラー f2(c); // OK }
関数の引数としてimmutableを使えば、呼び出し元もimmutableであることが保証されます。
この機能は、主にマルチスレッドプログラミングで使われます。
D言語を使う際には、このふたつの違いに気をつけてください。
担当:美馬(最近はD言語の破壊的変更が少なくて悲しい)
immutableは値をどこからも書き換えできないように、constは修飾した変数からのみ値の書き換えを禁止するものです。
immutable修飾された変数の値は、別の参照からも書き換えできません。
int i = 5;
immutable n = &i; // シンボル i から値を書き換えられてしまうので immutable が成立しない。よってエラーとなる
しかしconstは修飾した変数からのみ書き換えができないだけなので
int i = 5;
const n = &i; // ポインタ n から値の書き換えはできないが、 シンボル i から書き換え可能。
これは通ります。要するにconstはimmutableのサブセットなので
immutable i = 5;
const n = &5;
としても矛盾しません。反対に、const変数に対するimmutableな参照は作成できません(constでは他の書き換え可能な参照が無いことが保証できない)。
関数の仮引数向けにinキーワードというのが用意されていて、これはconst scopeと同じ働きをします。仮引数でconst修飾するよりinを使ったほうがいいでしょう。
// http://www.kmonos.net/alang/d/function.html#parameters
void foo(in ubyte[] b);
constやimmutableはメンバ関数に対しても適用できます。詳しくは以下。
http://www.kmonos.net/alang/d/const3.html
ちなみに、C++とDでは修飾子の挙動が異なります。
// C++ではポインタのアドレスのみ書き換え不可となるが、
// Dではポインタのアドレスも参照先の値も書き換えることができなくなる
const(int*) i;
http://www.kmonos.net/alang/d/const-faq.html
こちらではC++のconstは「頭部const」、Dのconstは「推移的const」とされているようです。
タイトルに見合った説明がなされていないようだったので、説明ベタですがツッコんでみました。
訂正です。
immutable i = 5;
const n = &5;
という部分がありますが正しくは
immutable i = 5;
const n = &i;
でした。
通りすがりさんの言う通り、かなり説明不足でした。
>> 関数の仮引数向けにinキーワードというのが用意されていて、これはconst scopeと同じ働きをします。仮引数でconst修飾するよりinを使ったほうがいいでしょう。
今回はconstのお話なのでconstを使いましたが、確かにinを使った方がいいですね。
しかし、inにはrefと同時に使えないと言うバグがあり、その場合はconst refとする必要があります。
といってもこのバグは現在は修正されて、in refとしてもconst refと同じように解釈されます。
>> ちなみに、C++とDでは修飾子の挙動が異なります。
>> C++のconstは「頭部const」、Dのconstは「推移的const」とされているようです。
今回はD言語のconstとimmutableの比較だったので推移性には突っ込みませんでした。なぜなら、constもimmutableも推移的だからです。しかし、それなら推移的でないC++のconstの話はするべきではなかったですね・・・
個人的に思うのですが、基本的に、読み取り専用を表すときはconstのみを使えばいいと思います。
しかし、以下のような場合があります。
関数の引数をconstで修飾しても、関数を呼び出す側はconstと非constの両方を渡せてしまう。
これは、非constの値がconstに暗黙的にキャストされてしまうのが原因です。
この挙動は、関数の呼び出し側でもconstな値を関数の引数として取りたい時不便です。
非constからconstへの暗黙的キャストが行われないconstが欲しいですね。そう、それがimmutableです。
そんな訳で、immutableは主に、関数を呼び出す側でも読み取り専用である値を関数の引数として取りたい時に使われます。
その機会が、マルチスレッドプログラミング時に現れます。メッセージパッシングのメッセージなどは、immutableでないといけません。
メッセージプールに溜まっている間にメッセージの内容が変わってしまったら非常に困りますね。
実は、D1の頃はconstしかありませんでした。上のような場合に不便だったので、D2でimmutableが導入されたのだと思います。
そして、constが普通の値とimmutableな値の橋渡しをする役割に落ち着いたんだと思います。
このように、それぞれの役割を見てみると、constとimmutableの違いがよくわかりますね。
このことに関しては、また新しく記事を書く予定です。