1 ポイント 投稿者 GN⁺ 2025-10-05 | 1件のコメント | WhatsAppで共有
  • AdaRustの2つの言語を使って Advent of Code の問題を解きながら、生じた違いや特徴を比較している
  • 安全性と信頼性を軸に、両言語の言語設計と実際のプログラム作成方法の違いを分析している
  • 各言語の標準ライブラリ、機能の組み込み有無、性能差、エラー処理スタイルなど、さまざまな観点で差異が明らかになる
  • モジュール性、ジェネリクス、ループ、エラーハンドリングなど、実践的なコード例を通じて、実際の記述・運用時に経験する具体的な事例を説明している
  • 静的型付けの方式、配列処理、エラー処理インターフェースの違いによって、開発体験の差が際立っている

紹介と目的

  • Advent of Code(以下 AoC)の問題を解く過程でAdaのみを使っていたが、2023年からはRustModula-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::FileBufReaderを活用する
  • ファイルオープン時は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件のコメント

 
GN⁺ 2025-10-05
Hacker Newsの意見
  • Adaには本当に優れたアイデアが多くあったのに、その大半が安全性が極めて重要な分野でしか使われなかったのは惜しい。特に数値型の値域を制限する機能は、特定のバグの予防にとても有用だ。Spark Adaは学びやすく、実際にSIL 4(最も厳格なソフトウェア安全基準)準拠ソフトウェアの開発にも適用しやすかった。この数十年、ソフトウェア業界は「成長優先、安定性は後回し」に突き進んできたが、今は再び安全なソフトウェア開発へ戻ろうとする流れを感じる。これまで蓄積されてきた安全性に関する教訓が、より良い言語につながってほしい。現実には、良いアイデアがマイナー言語の中に埋もれて消えていくことが多かった
    • ソフトウェア開発を長くやっていると、「車輪の再発明」が本当に多いと感じる。AdaとRustはどちらも安全性を志向している点では似ているが、その定義と適用範囲は異なる。Rustは非常に絞り込まれた形の重要な安全性を強烈に追求しており、Adaはより広く具体的な安全性の定義を持っている。私が90年代初頭にAdaを学んだとき、最も一般的な批判は、言語が大きすぎて複雑すぎるため開発速度を落とすというものだった(当時のAda 83認証コンパイラは、1人あたり現在の価値で約2万ドルした)。しかし時代は変わり、Rustのように大きく複雑な言語が、実際に安全な並行プログラミングのために必要だと皆が認めるようになった
    • NimもAdaとModulaに触発され、型の値域を制限するsubrangeをサポートしている
      type
        Age = range[0..200]
      
      let ageWorks = 200.Age
      let ageFails = 201.Age
      
      コンパイル時に、201はAge型に変換できないというエラーが出る
      Nim Subrangesの説明リンク
    • Ada(GNAT基準)はコンパイル時の物理単位・次元解析(つまり単位チェック)をサポートしている。エンジニアリングの現場では非常に実用的なのに、なぜ他の言語はこうした重要な機能をサードパーティーライブラリでしか提供しないのか不思議だ
      関連ドキュメント
    • C++でも値域が制限された数値型はコードで簡単に作れる(標準ライブラリにはないが、自前実装はとても簡単だ)。一部の安全性チェックは実行時ではなくコンパイル時にできる。こうした機能はすべての言語が標準でサポートしてほしい
    • Adaでいちばん惜しいのは、オブジェクト指向(OOP)に対する明確なアプローチだ。多くの言語はOOPの概念を「クラス」というひと塊に押し込めるが、Adaではメッセージ送信、動的ディスパッチ、サブタイピング、ジェネリクスなどを個別に選んで適用できる。それぞれの機能がきれいに組み合わさるのがとても気に入っていた
  • 著者は、Adaには公式仕様がありRustにはないことなどを違いとして挙げているが、実際の利用者にとっては、それ以上に言語の採用状況やエコシステム(ツール、ライブラリ、コミュニティ)のほうが重要だ。Adaは航空宇宙や安全分野などで成功しており、AOCや低レベルの組み込み作業にも向いているが、実際のプロジェクト(分散システム、OSコンポーネントなど)では、データ形式、プロトコル、IDE対応、同僚との協業といった要素の比重が大きい。結局、最初に言語を選ぶときは、こうした環境面が決定的になる
    • 最近Rustも、FerroceneでAdaの仕様スタイルを参考にしたスペック文書を寄贈している。公開されているので参照できる
      Rust仕様
    • RustとAdaはどちらも、厳密な意味での「形式的(formal)仕様」(機械的に証明可能な文書)としては弱い。Spark Adaですら言語意味論上の仮定に依存しており、それでさえ完全に公式で機械可読な形ではない
    • 航空機制御ソフトウェアの開発者たちも、「現実の実運用環境で重要でないことなら、私たちのプロセスは確かに過剰でしょう」と答えるはずだ。実際、安全性が重要な分野では、Adaのような厳格な言語とプロセスこそがむしろ標準だ
  • AdaはRustより型関連機能では劣るとしても、コードの可読性という点ではAdaのほうが優れていることが多い、という点が印象的だった。比較記事ではコンパイラ速度への言及が抜けていたが、Adaが複雑な言語だと感じられていたのは昔の話で、今のRustと比べれば必ずしもそうではないかもしれない。この記事を読んで、Adaで本物のプロジェクトをやってみたくなった
    • 「型関連の欠点」が正確に何を意味しているのか気になる。私の経験では、Adaの型システムは非常に表現力が高い。ユーザー定義の値域型、任意の列挙型で添字付けできる配列、型ごとの演算子定義、コンパイル時・実行時チェック、事前条件・事後条件など、型にさまざまな補助機能を追加できる。判別レコードや構造体表現句などもある。むしろ欠点ではなく強力な機能だ
  • AdaとRustの文字列の違いについて話したい。Adaは1980年代初頭の設計時に、文字(char)配列を「文字列」としていたため、単なるバイト配列としてインデックスしやすい。Rustは基本的にUnicodeを意識した設計なので、Rustの文字列はUTF-8でエンコードされた、つまり本当の「テキスト」だ。だからAdaは配列のようにランダムアクセスできるが、Rustでは文字列の概念が違うので、単にバイト配列へ変換するという選択肢もある
    • Adaの組み込みUnicode文字列は一般にUTF-32配列だ。Rustと違ってUTF-8リテラルを直接提供せず、8/16/32ビット配列から変換する必要がある
    • Rustの文字列でもインデックスは可能だ。ただしRustは文字列を通常の配列のようには扱わず、主にsub-stringスライスを使う。特定の文字の途中で切ってインデックスするとpanicが発生する(Unicodeの符号化境界を破るケース)。AoCのように常にASCIIしか使わないなら、[u8]やstr::as_bytesメソッドでバイトスライスを使うのが適切だ
  • Rustが「並行(concurrent)プログラミングを基本サポートしていない」という著者の発言は奇妙に感じる。Rustはスレッド(thread)機能が言語に組み込まれており、むしろasyncより使いやすい。非常に大量のスレッドが必要でリソース上限にぶつかる場合だけが問題であって、大半のソフトウェアではbuilt-inのスレッドで十分だ
    • (Rust非利用者として純粋に知りたい)Rustではスレッドとasyncでのキャンセル処理はどう違い、他言語のasyncとどんな違いがあるのだろうか。C++、Python、C#ではasyncの中断管理はスレッドよりはるかに優れていた。Rustではこうしたキャンセルや中断管理を例外として扱わないため、むしろ少し難しいと聞いたことがある。実際の現場での経験が知りたい。Adaではこうした中断管理がどうなっているのかも聞いてみたい
    • Tokioのようなワークスティーリングスケジューラが、単に複数のスレッドを回すより実際どの程度速いのか、その境目が気になる。単純な配列(たとえばVecMap)も要素数が少なければ速いが、ある程度を超えると別のデータ構造のほうが効率的になるのと似ていると思う。実際どのあたりからワークスティーリングが有利になるのか気になる
    • 現実的には、Asyncを使う主な理由は、使っているサードパーティーcrateがAsyncだからだ。(例: ReqwestはTokioを必要とする)高水準のアプリ開発で非Asyncだけにこだわると、結局どこかで限界にぶつかる
    • プラットフォーム側のスレッド対応が弱い環境(WASM、組み込みなど)では、むしろAsyncのほうが適している。何十万人もが同時にブログへアクセスする状況は非現実的で、むしろそうした場面でAsyncの必要性を強調するのは大げさに思える
  • Adaにもオープンソースコンパイラがあると知って興味を持った。以前は独占的なコンパイラしかないと思い込んでいて、そもそもAdaに関心を持たなかったのだが、もう一度確認してみたくなった
    • GNATコンパイラはリリースから30年以上たっており、かつてはGPLランタイム例外がなかったため、コンパイル成果物までGPLにしなければならないという誤解があったが、現在はその問題は解消されている
    • GNATは90年代からGCCベースで作られており、一部の大学ではリアルタイムプログラミングのような実習中心の科目でGNATが直接使われていた。Adaをプログラミング入門用に使おうとして、すぐPascalやC++に切り替えた経験もある
  • 3Dプリンティング分野で注目していたプロジェクトの中では、最近Pruntというプリンタ制御ボードとファームウェアがあった。Adaでファームウェアを開発しており、かなりユニークだが概念的にはよく合う選択だ
    Pruntホームページ
    Prunt GitHub
  • Case Study 2の終盤で、「クライアントがSIDE_LENGTHを知る必要があるなら、それを返す関数を追加せよ」とあったが、わざわざ関数にしなくても pub const SIDE_LENGTH: usize = ROW_LENGTH; のように定数宣言を書くほうが直接的だ
  • 両言語がどちらもスタック中心のプログラミングを奨励している、という主張には同意しない。Adaはむしろ静的割り当て方式を積極的に推奨している
  • Adaの配列インデックスが任意の型にできる点が大きな利点のように紹介されていたのは興味深かった。ほとんどの言語には辞書(ハッシュマップ)が標準ライブラリにあり、Rustにも2種類ある
    • ここで言っているのは言語組み込みの配列の話だ。たとえばAdaでは、「eggs」という配列のインデックスをBirdSpecies型に指定すると、eggs[Robin]やeggs[Seagull]は意味を持つが、eggs[5]は許されない。Rustでも、望むデータ構造(例としてIndex<BirdSpecies>実装)を作ればeggs[Robin]はできるがeggs[5]はエラーになる。ただしRustは言語自体としてこれを「配列」として直接サポートしてはいない。Adaのように「ユーザー定義型を部分集合付き整数型として宣言できる」とき、こうしたインデックス指定は真価を発揮する。Rustでは、値域制限整数のような純粋なユーザー定義型はまだ作れない(内部的にNonZeroI16などが提供されているだけだ)。Rustがこのレベルまで対応してくれたら本当にうれしい
    • Adaにもハッシュマップや集合構造は標準である。Adaコンテナ関連標準(A.18節参照)。配列インデックス型に典型的な「連続した値」(例: 0〜N-1)の範囲を使える点は、密なマップや連続メモリアクセスが重要な状況では、辞書よりはるかに高速でキャッシュ効率も良いため利点になる
    • Adaにおける配列インデックス型の制限(subtype)は、辞書とは構造的にまったく別の概念だ。配列インデックスに使える値の種類そのものを言語レベルで制限できる