1/29/2017

Rust対Go (NTPsecプロジェクト)

NTPsecプロジェクトが興味深い記事をブログに投稿している。NTPを実装するための言語としてGoを選んだ理由。

この投稿は、NTPsecの目的のためにRustとGoを比較したものである。NTPのコードベースを踏まえれば、OSカーネルとしてのハードコアなシステムプログラミングの問題というより、ソフトリアルタイムな重大な領域を持つ独特な特徴の組み合わせを持っていることが特色である。

個々の軸での比較

これは外面上の見方である。Goでプログラミング経験をした後で、私がRustを使う前に書かれたものである。従って、この節のRustの部分はマニュアルから抽出した理論である。この節の後に実践後の報告を盛り込んだ。

学習曲線(ラーニングカーブ)

NTPの潜在的な貢献者のベースは主にCプログラマである。従って、利点はその母集団にとっての学習曲線がより容易な言語だということだ。

Goは実のところCプログラマにとって簡単なツールアップグレードとなるよう設計されており、その目的は成功している。私は始めて4日あまりでGoに堪能になることができた。Rustは4日で、私はデータ所有権とオブジェクトシステムの完全な理解するのさえ、もちろんそれらを利用するのさえ、まだ四苦八苦していた。これはGoにとって+1である。

Cだけではなく、どこからでもアップグレードが難しいことがRustを促進していない。Rustの所有権システムによって要求される多数の複雑さと決まりごとが高いハードルで、私はそれが本当に良い準備であることを知る他の言語は存在しない。

変換の程度

我々はどちらか一方の言語に移すコードが6万2千行(62KLOC)ある。それは多いし、手で変換することが簡単なことを重きに置いている。単にCにRustの所有権/ライフタイム注釈を加えることは大きなな作業になるという理由だけで、ここでGoは+1を得る。

しかし、これは逆転できる。我々はいつでも移転できる時に、最初に試すことの一つは、Corrode経由でRustへの自動変換だろう。それがうまくいくなら、Rustにとって+3のような大きな利点になるだろう。しかし、Goのほとんどマニュアル化されていないc2goが役立つと分かれば、再び逆転する可能性がある。

平行性

(注意: メインアルゴリズムはもともと連続するランデブーを持っているが、非同期で実行されなければならず、任意の遅延でDNS検索を行う必要があるため、これはNTPにとって重要である。)

Rustはデータ所有権システムを介して共有状態をロックする従来型のミューテックスと同等のものをサポートする。CSPチャネルの実装もある。GoはコアなプリミティブとしてCSPチャネルを持っており、ライブラリ関数を通して従来型のミューテックロックを設定する機能もある。

Rustのネイティブな共有状態/ミューテックシステムは、CSPと比較して煩雑で複雑過ぎるように見え、一連のプリミティブはどのような言語でも既知のデフェクトアトラクタである。私は Rustデザイナーの評判にCSPを含めることで軌道修正することを考えているが、Goは最初にこの権利を得て、その結果はコア言語にうまく統合されている。これはGoに+1である。

組み込みデプロイメント

Rustのオーバーヘッドのない抽象化は、明らかにGoのランタイムやファットバイナリよりも制約があって組み込み可能なシステムをデプロイするのにうまくマッチする。これはRustにとって明らかな+1である。

しかし、Android開発プラットフォームとしてGoを使うためにこれを修正するというGoogleのビジネスケースが強く、Goの開発チームは将来の方向性としてこれを明言していることは注目に値する。GC(ガベージコレクション)を持つ言語はRustのように組み込みにうまくマッチしないが、Googleが十分に巧みに振る舞えば、Rustの利点が部分的には不安定になる。そして、Goのチームにはデプロイに多くの巧みさがある。

レイテンシとソフトリアルタイム・パフォーマンス

再び、オーバーヘッドのない抽象化(zero-overhead abstractions)やストップ・ザ・ワールドGC一時停止がない点はRustに明確な+1を与える。

ここでGoが完全に不適格に近付いたことに注目に値する。危険域の中でGoのGCをロックアウトできないなら、Rustはデフォルトで勝つだろう。NTPのチャレンジがハードリアルタイムに向けて少しでも傾いたり、危険域がそれほど閉じ込められるなければ、Rustがデフォルトで勝つだろう。

セキュリティと安全性

RustとGoは、Cによく見られるバッファオーバーランの類や状態参照バグを排除することを目的とする強力な型システムを持っている。彼らは証明可能な正確さのRAIIライクなモデルに関してGCに頼るGoでは様々なアプローチを取っている。

ここでRustは+1を得る。なぜなら、実際のところ容易に監査できないランタイムに頼るよりも, あなたがそれについて推論することの方が普遍的で優れて見えるからだ(原理的に可能なオープンソースだとしても)。

Rustの中からの見解

Rustの中で私が選択した学習プロジェクトは、単純なIRCサーバを書くことだった。実際に睡眠中にコード化できるような状態マシンの周囲に書かれたリアルタイムを必要としないサーバデーモンであれば、NTPの良い準備運動になると考えた。

私は試みに次に4日間の要約を書いた。

実際には、私はRustを使い勝手が最悪寸前まで痛いと感じた。学習曲線は私が予想していたよりもはるかに悪かった。サーバのラッパーのコード67行を書くのに不十分なマニュアルで苦労し、4日間も掛かってしまった。

文字列連結のようにRustでは超簡単なものでさえ、不合理なほど(unreasonably)難しい。言語はあなたが何かをできるようになるまでに、大量の面倒で難解な儀式(ritual)を要求する。

Goとの相違は極端である。Goの探求の4日間で、私はほとんどのプログラミング言語をマスターし、プログラムやテストを行い、好みの特徴を追加した。

その次に、私はネットワークサーバを書くために絶対的に不可欠な機能がRustには明らかに欠けていることが分かった。このバグレポートをじっくり考えてみて欲しい: "select/poll/epoll_wait"のようなAPIはあるか? そして、この回答を聞いてみてほしい:

我々はepoll/selectの抽象化を持っていない。今の答えは「ソケット単位にタスクを生成する」ことである。

更に調査をしたところ、私はコア言語の中で実際にこの問題を修正するための提案がないことが分かった。コメントは、サードパーティ製クレート(Rustはcrateという単位でプログラムを開発する)には寄せ集めのハーフソリューションがあることを認めているが、どれを採用するかについてのコンセンサスを説明していない。

私の個人ブログでこの批評についての考えを公開した後、Rustコミュニティの様々な思慮深いメンバーからたくさんのことを学んだ(同じようにあまり思慮深くないメンバーからも多くの非難を集めた)。

現時点で、より思慮深いRust専門家によって認知されているの以下の問題がRustをNTPの実装言語として不適当なものにしている:

  • 10年のタイムスケールで安定することを期待するコアAPIを表現するには、この言語はまだ十分に成熟していない。

  • これまでの特例で、select/epollのようなNTPにとって必要な昔ながら機能は、言語の安定部分ではない。クレートシステムの中で実装が存在しているが、10年のタイムスケールでメンテナンスされるであろう代替案の保証がまだない。

  • 言語のマニュアルはRustコンパイラについてはよく説明されているが、 生産ツールとしてその言語を利用するために絶対に必要なクレートシステムの各部との説明のギャップを埋めることができない。

  • 全般的に、実際の言語についての多くの極めて重要な情報がまだ全くマニュアル化されていない。そして、それを学ぶには開発グループの大勢の一部となってコミュニケーションチャネルをフォローしなければならない。

これらの問題は、いつクレートに感謝し、安定性保証を持ったコアAPIの一部としてそれらを指定するかどうかについて、Rustコミュニティの中の社会的/人間的な混乱を反映しているように見える。Rustにはコンウェイの法則がある: クレートシステムの分散構造は、とにかくこの手のコミットメントを作るのをやめさせる傾向にある。

Goは完全に似ていないパッケージシステムを持っているというわけではないが、Goの設計者はとりわけネットワークサービスデーモンを実装するために必要となる全てのプリミティブを含み、Rustのものよりはるかに豊富なコアモジュール一式を定義する選択を早くから行った。

更に、コアのRust言語はマニュアルや入門書が十分ではないという深刻な学習曲線問題を抱えている。関連して、borrow checkerのような重要な機能の干渉コスト(friction cost)がかなり高い。これはNTP開発者にとって参入障壁に繋がっている。

このゲームは割が合うかどうかについて、いくつかの疑問がある。Rustの理論やそのオーバーヘッドのない抽象化に焦点を当てることは、ハードリアルタイム・アプリケーションやOSカーネルにとって意味がある。そして、これはネットワークサービスデーモン、まさにNTPの実装のようなソフトリアルタイムの重要な領域のデーモンを書くには正しく重要だという点がはっきりしていない。

結論

比較のために、私はRustからGoに切り替えたところ、同じ時間で、<100 LOCの部分的な実装作業でさえも苦労して、IRCプロトコルの状態マシンを終了するだけのIRCサーバのソケット全てのfuと並列処理の完全なエクステリアを書いてテストすることができた。

その教訓は非常に明確である。GoはRustよりも我々の要件により適している。ほど遠い。

更新: 私がRustが一般的に役に立たないと考えている意味ではない。まだ成熟したバージョンではないが、いくつかのニッチなものにかなり有効であると思う。例えば、ハードリアルタイム、OSカーネル、特に宇宙航空や医療のような極めて高い確実性を伴うファームウェアが高い成果に証明するだろう。

Hacker News

更新: Rustの2017年のロードマップHacker News