私はCrystalというプログラミング言語が大好きで、この2〜3年ほどコマンドラインツールの開発に取り組んできました。その中でRubyとの違いに戸惑い、いろいろな発見や壁にぶつかりました。この記事ではそれらをまとめます。
1. Rubyとの文法的な近さ
CrystalはRubyと驚くほど文法が似ています。Rubyでよく使う記法の多くはCrystalでそのまま動きます。Crystalは静的型付き言語ですが、多くの場合型を明示しなくても型推論で補ってくれます。
2. DeepWikiを活用しよう
DeepWiki はCrystalの学習に非常に役立ちます。
マイナー言語にとっては最良のリソースです。日本語でも質問できます。
3. 配列やHashに異なる型は混ぜられない
Crystalでは、Array
や Hash
に異なる型を混在させることはできません。ユニオン型を使うこともできますが、通常は避けたほうがよいでしょう。Rubyのように「配列やハッシュを無名の構造体のように使う」ことはできません。その代わり以下を検討してください:
- 小さなクラスや構造体を作成する
-
record
を使う(簡易的な構造体) - 一時的なら
Tuple
を使う
最初は不便に感じるかもしれませんが、慣れると気にならなくなります。
4. eval
は使えない
Crystalにはeval
がありません。これはRubyとの大きな違いです。Crystalにも苦手なことはあります。evalが必要な場合は素直にRubyやPythonを使いましょう。もしどうしても動的にコードを評価したいなら、mrubyを埋め込むか Anyolite のようなライブラリを利用する必要があります。Crystal自体にもインタープリタはありますが、実用的ではありません。また速度もRubyやmRubyよりも遅いです。
5. メソッドのオーバーロード
Rubyでは1つのメソッドの中で引数の型を見て分岐させることが多いですが、Crystalではオーバーロードを活用するのが自然です。ブロックあり/なしでオーバーロードが可能です。
6. 戻り値の型は統一する
Rubyでは戻り値の型が複数あっても問題になりませんが、Crystalでは戻り値の型が不明確だとコンパイルエラーになります。もし複数の型を返したいなら、メソッドを分割しましょう。
7. Nilの扱い
変数がNil
を取り得るかどうかに注意してください。
もしNil
を許容するなら、not_nil!
や if val = nilable_val
のようなイディオムを使う必要があります。
8. ガベージコレクション
CrystalはLLVMベースの言語で、外部GC(libgc
)を利用しています。
パフォーマンスは多くのケースでRustやNimに匹敵しますが、メモリプロファイリングやチューニングは難しいことがあります。また、GCの実行タイミングは予測できないため、リアルタイムシステムには不向きな場合があります。
9. 非同期I/O
Crystalはもともと内部でlibevent
を利用しており、デフォルトで非同期I/Oが使えます。この点について「Rustより書きやすい」と感じる人もいます。さらに、Crystal 1.15 からlibeventへの依存が不要になりました。
10. 配布時のリンク
Crystalのプログラムは基本的にlibgc
にリンクされ、多くの場合libevent
にも依存します。
そのため、バイナリ配布には注意が必要です。
- Linux: GitHub Actions + Docker + muslで静的リンクが可能
- macOS: Homebrew Tapを用意するか、依存ライブラリ(
libgc
,libevent
,pcre
など)を静的リンクしたポータブルバイナリを作成する
11. Windowsサポート
Crystalは最近、Windows(MSVC / MinGW64)でも安定して動作するようになりました。並列実行も動きます。ただしCライブラリ依存の解決なかなか苦痛を伴うケースが多いです。Windowsに詳しくない場合は、AIに尋ねながらがんばる必要があります。
12. OptionParserの制限
標準ライブラリのOptionParser
は複合オプションに対応していません。
つまり ls -l -h
は使えますが、ls -lh
は使えません。
これについては、自分でプルリクエストを作成して近い将来に解消したいと考えています。
まとめ
Crystalでコマンドラインツールを作るのは、ときには痛みを伴います。しかし、そのかわり勉強になることも多いです。Crystal言語の「最良の日々」は過去や現在ではなく、未来にあると確信しています。