AoC問題の解法を通じたAdaとRustの比較
(github.com/johnperry-math)- AdaとRustの2つの言語を使って Advent of Code の問題を解きながら、生じた違いや特徴を比較している
- 安全性と信頼性を軸に、両言語の言語設計と実際のプログラム作成方法の違いを分析している
- 各言語の標準ライブラリ、機能の組み込み有無、性能差、エラー処理スタイルなど、さまざまな観点で差異が明らかになる
- モジュール性、ジェネリクス、ループ、エラーハンドリングなど、実践的なコード例を通じて、実際の記述・運用時に経験する具体的な事例を説明している
- 静的型付けの方式、配列処理、エラー処理インターフェースの違いによって、開発体験の差が際立っている
紹介と目的
- Advent of Code(以下 AoC)の問題を解く過程でAdaのみを使っていたが、2023年からはRustとModula-2でも解答を書くようになり、直接比較することになった
- 既存の Ada 中心のソリューションを Rust に移し替える中で、両言語の構造的な違いと固有のアプローチを実感した
- コードの安全性、信頼性、言語設計の観点から、実際の利用上の違いを明確にすることを目的としている
比較に使用した言語バージョン
- Ada 2022(必要に応じて Spark 2014 の一部規則も参照)
- Rust 2021(主な比較は Rust 1.81.0 ベース)
除外した機能と比較基準
- 各言語の代表的な機能(=キラーフィーチャー)は本文中のコメントで簡潔に触れられている
- 個人的な経験や各ソリューションにおける現実的な必要性に応じて、扱っていない機能も一部ある
- 可能な限り私見は排除し、主要な特徴に集中している
著者の背景と視点
- Ada、Rust ともに非母語話者の利用者として、C/C++、Pascal、Modula-2 など 1980 年代の言語経験をベースとしている
- その結果、コードスタイルが現代的なものやイディオムと異なる可能性がある
- 最適な実装ではない場合もあり、状況によっては直感的または慣例的でない解法を選んでいることもある
AdaとRustの位置づけ
- 現在でもAdaは非常に安全で信頼性の高いシステム/組み込み開発言語であり、コードの可読性を重視している
- Rustはメモリ安全性とシステムプログラミングの強みを持ち、Stack Overflow の開発者調査で長年にわたり「最も好まれる言語」に挙げられている
- Ada は汎用高水準言語として、読みやすさと保守性に特化した特性を備える
- Rust は低水準システムプログラム開発を志向し、明示的なメモリ管理とエラー/オプション型に基づく安全なプログラミング文化を確立している
安全性および構造的特徴の比較
-
Ada
- ISO 標準(厳密な仕様)
- 問題の特性に合った型(範囲、桁数など)を宣言しやすい
- 配列インデックスが数値でなくても許容される
- Spark というさらに厳格な仕様が存在する
-
Rust
- 仕様は公式ドキュメント(Reference)とコンパイラ中心
- 型宣言はマシン型(例: f64, u32)に依存する
- 配列インデックスは数値型であるのが自然
機能/組み込み有無テーブルの主な要約
- 配列範囲チェック、ジェネリックコンテナ、並行性、ラベル付きループ、パターンマッチングのサポートなどに違いがある
- Ada はException(例外)ベースのエラー処理、Rust は Result/Option 型による返り値ベースの処理方式を採用する
- Rust はマクロ、パターンマッチング、関数型的な純粋性のサポートなどで差別化が際立つ
- Ada は契約による設計および DBC(Design By Contract)のコンパイル時検証を Spark でサポートしている
- メモリ安全性の面では Rust と Spark は強制されるが、Ada は Null ポインタの利用を許容する
性能および実行時間の比較
- Rust は一般に実行時は速いがコンパイルは遅く、Ada は逆にコンパイルは速いが実行時は検証チェックに応じてやや遅いと評される
- ベンチマーク結果では、Rust は day24 問題で f64 型の限界によりオーバーフローが発生した一方、Ada は digits 18 のような高水準の型指定が可能で、マシン型の自動選択とオーバーフロー回避によって優れた性能を示した
- Rust では安定していない f128 あるいは外部ライブラリを使う必要があるが、Ada はコンパイラ仕様に適合した型指定だけで優位性を確保できる
ファイル処理とエラーハンドリング(Case Study 1)
Adaのファイル処理
- 基本的にAda.Text_IOを使用する
- 明示的なファイルオープン、行単位の読み取り、必要な範囲、位置ベースの行処理などが比較的直感的に行える
- エラー発生時は明確なエラーメッセージよりも例外で処理され、関数シグネチャにエラー発生の可能性が表れない
Rustのファイル処理
- std::fs::FileとBufReaderを活用する
- ファイルオープン時はResult型で返され、エラーの可能性が明確に示される
- 文字インデックスへの直接アクセスはサポートされず、必ずIteratorで処理する
- map, filter, collect, sumなどの関数型・反復型ツールが中心で、さまざまなマクロ(例: include_str!)も利用できる
- 返り値型に明示的にエラーを宣言することで、関数レベルでのエラー伝播の明確性が確保される
モジュール性とジェネリクス(Case Study 2)
Adaのモジュール性
- **パッケージ(package)**ベースで、明確な仕様(インターフェース)と実装を分離する
- モジュール性を高めるため、下位パッケージやuse/rename構文の組み合わせで可読性を調整する
- パッケージのgenericをサポートし、型・定数・下位パッケージ全体を一般化できる
Rustのモジュール性
- mod/crate体系でモジュールを構成し、仕様と実装の区別はドキュメント生成器が自動化する
- pub/privateのアクセス指定による宣言的な可視性制御
- use/asでインポートや名前変更を組み合わせる
- 組み込みテストサポートにより、コード内に直接テストモジュールを宣言してビルド・自動実行できる
ジェネリクス
- Ada はパッケージ/プロシージャ単位のジェネリクスのみをサポートし、型単体ではサポートしない
- Rust は型自体にジェネリクスを適用できる(テンプレートベース)
- Ada は型の範囲などの追加特性を範囲型、サブタイプなどで明確に表現でき、Rust ではインスタンス定数を活用する
列挙型の比較(Case Study 3)
- Ada は簡潔な宣言と同時に、自動的に離散型・順序型・反復ループ/インデックス利用をサポートする
- Rust の enum も宣言自体は似ているが、パターンマッチングや反復などの扱いにはより明示的な方法が必要になる
結論
- 高水準の仕様型、検証可能性、実行時チェックなどでは Ada のほうがより厳格な制御力を提供する
- 関数型プログラミングスタイル、マクロプログラミング、コンパイラ補助のエラー処理などでは Rust が開発のしやすさと安全性の両面で優位に立つ
- 実践的な問題解決において、Ada は古いコードとの互換性や保守性に強みがあり、Rust は現代的な開発ツール生態系と安全性/並列性の支援に利点を持つ
1件のコメント
Hacker Newsの意見
Nim Subrangesの説明リンク
関連ドキュメント
Rust仕様
str::as_bytesメソッドでバイトスライスを使うのが適切だPruntホームページ
Prunt GitHub
関連HNコメント
pub const SIDE_LENGTH: usize = ROW_LENGTH;のように定数宣言を書くほうが直接的だIndex<BirdSpecies>実装)を作ればeggs[Robin]はできるがeggs[5]はエラーになる。ただしRustは言語自体としてこれを「配列」として直接サポートしてはいない。Adaのように「ユーザー定義型を部分集合付き整数型として宣言できる」とき、こうしたインデックス指定は真価を発揮する。Rustでは、値域制限整数のような純粋なユーザー定義型はまだ作れない(内部的にNonZeroI16などが提供されているだけだ)。Rustがこのレベルまで対応してくれたら本当にうれしい