3-a2. D 2.x と D 1.x

初出: 2007/10/26
最新: 2008/02/05
[3. D言語各論] に戻る

目次

現在D言語は、新しい機能を追加するためのブランチ「D 2.x」と、 2007年1月時点の機能で固定して、バグ修正のみが行われるブランチ「D 1.x」に分かれています。 ここでは、執筆時点の最新版(2.010と1.026)がどう違うのか等について書いていきます。

取り巻く環境の違いとしては、外部のツールやライブラリはまだまだ1.xベースのものが多く、 2.xに持ってくるとコンパイルが通らない…ということが多々あります。"言語仕様の違い" の項で述べるような文字列型の仕様変更を反映すれば使えるようになりますが、 そんな手間を掛けたくないという方は今しばらく 1.x を使った方がベターかと思います。 これから新しくD言語の勉強を始めたい!という方には、個人的には 2.x がオススメです。 今後主流になっていくのは 2.x ですし、今まさに言語仕様が議論され実装されている最中の ホットな系統の方が、学んでて楽しいと思います。(^^; まとめると、

言語仕様の違い (const/invariant)

概要

今後さまざまな拡張が予定されていますが、現時点での最大の違いは const, invariant の導入です。 D 1.0 で通るプログラムのほとんどが、この変更のために、D 2.0 ではコンパイルが通らなくなっています。 具体的には、この導入に伴って、D の文字列型の定義が

alias char[] string; // D 1.x

から

alias invariant(char)[] string; // D 2.x

に変更されました。つまり、D 1.x の文字列は "charの配列" という定義でしたが、 D 2.x では "charの書き換え不可の配列" になっています。 一般に文字列を書き換え不可にする利点については yarv-dev での shiroさんの考察 が参考になります。

具体的は

文字列を1文字単位で書き換える操作ができなくなりました。コンパイルエラーです。

string str = "Hello";

str = "World";       // 2.xでも1.xでも OK 
str = str.toupper(); // 2.xでも1.xでも OK 

if( str[0] == 'W' )  // OK 
  for(int i=0; i<str.length; ++i)
    str[i] = 'A';    // 2.x ではエラー!!!

2.xの標準ライブラリの実装は全て適切に宣言し直されているので、 標準ライブラリ関数で文字列操作をする場合は1.xでも2.xでも差はありません。 1文字ごとの書き込みを行いたい場合は、string 型ではなく、char[] 型の配列を確保して、そこに書き込みます。

import std.contracts;

...
char[] buf = new char[str.length];
for(int i=0; i<str.length; ++i)
  buf[i] = 'A';    // OK

str = buf.idup; // あるいは str = assumeUnique(buf);

char[] 型の idup プロパティを呼ぶと、その時点のバッファの内容を固定した書き換え不可な文字列を返すので、 変数 str にそれを代入することが可能です。(あるいは、このあとbufの内容をもう書き換えたりしない場合は、 std.contracts.assumeUniqueを呼んで"bufを書き換え不可と見なす"ことも可能です。この場合余計なコピーが発生しません)

1.x → 2.x に移植

1.x 向けに書かれたコードでは、文字列を表す型として char[] が使われています。

  1. これを全部 string に直す。(char[]をstringに全置換でいいです)
  2. 1文字書き換えをしている箇所がコンパイルエラーになるので、上の要領で書き直す

という作業を地道にやると、2.x向けのコードになります。

言語仕様の違い (その他拡張)

本家によるまとめ

Closure

「本物の」クロージャが言語仕様に入りました。 関数内関数を、定義された関数の外に出た後でも使い回すことができます。 (D 1.x では、関数内関数は外に出すことができませんでした。) 「関数型言語って何がすごいんですか」 という記事の例を D で実装すると、こうなります。

import std.stdio;

void delegate() rocket(int n)
{
   // { ... } で無名無引数関数を作ってreturnしています
   return {
      if( n )
      {
         writeln(n);
         n--;
      }
      else
         writeln("liftoff");
   };
}

void main()
{
   auto f = rocket(3);
   auto g = rocket(3);
   f();  // 3
   f();  // 2
   f();  // 1
   f();  // liftoff
   g();  // 3
   g();  // 2
}

foreach range

従来の foreach(a; 配列) で配列(などなど)の要素を一個一個処理するループに加え、 指定した範囲の値を一個一個処理する構文が加わりました。構文として足すよりも Rangeオブジェクト みたいなものを導入した方がよかったのではないかという気もします。

import std.stdio;

void main()
{
   // 0,1,2,3,4,5,6,7,8,9,
   foreach(i; 0..10)
      writef(i, ",");
}

オーバーロード集合

複数のモジュールから同名の関数を import したときにも、 ある程度オーバーロードが解決されるようになりました。細かいことは仕様書をご覧下さい。

標準ライブラリの違い

本家によるまとめ

言語仕様の面と違い、全体的な書き直しを要求するような大きな変更はありません。 D 2.x では、std.contracts, std.getopt, std.variant など、 いくつか標準ライブラリのモジュールが増えています。よく使う部分としては、標準入出力のモジュール std.stdio に、 書式化しない一行出力関数 writeln などが増えました。

import std.stdio;

void main()
{
    writeln( "%%% Hello", 1, 2, 3, "World %%%" );
}

また、std.random にメルセンヌツイスターなど高速な乱数生成エンジンが追加されました。 std.conv の 文字列⇔値 変換関数群が "to" という名前に全てまとめられて見通しがよくなりました。

[3. D言語各論] に戻る

presented by k.inaba   under NYSDL.