世界を動かす技術を、日本語で。

SQLiteはなぜC言語でコーディングされているのか

概要

  • SQLite は2000年から C言語 で実装されているデータベースライブラリ
  • C言語 が選ばれている理由は 性能、互換性、依存性の低さ、安定性
  • オブジェクト指向言語や「安全な」言語への移行は現状メリットが少ない
  • 「安全な」言語(例:Rust)への移行には 多くの課題 が残る
  • 今後も C言語 で開発が継続される予定

SQLiteがCで実装されている理由

  • C言語高速なコード が書ける「ポータブルなアセンブリ言語」的存在
  • ほぼ全てのシステムで C言語ライブラリ を呼び出せる互換性
  • 依存性が極めて低い ため、最小構成なら標準Cライブラリの数関数のみで動作
  • C言語 は古くて安定しており、仕様変更による影響が小さい
  • SQLite のような低レイヤーで広く使われるライブラリに最適な選択

オブジェクト指向言語で実装されていない理由

  • C++やJava で書かれたライブラリは、同じ言語で書かれたアプリからしか使いにくい
  • C言語 なら、ほぼ全ての言語から呼び出し可能なライブラリ構築が可能
  • オブジェクト指向は設計手法であり、 C言語でも実現可能
  • 問題の分解方法として「手続き型」が有利な場合も多い
  • 初期開発時(2000年頃) はC++やJavaの成熟度・互換性に課題あり

「安全な」言語で実装されていない理由

  • RustやGo などの「安全な」言語はSQLite開発開始時には存在しなかった

  • 移植すると 新たなバグや性能低下 のリスク

  • 「安全な」言語は配列範囲チェック等のため 分岐が増え、完全なブランチテストが難しい

  • OOM(メモリ不足) 時の挙動がSQLite設計と異なることが多い

  • Rust は今後の候補になる可能性もあるが、下記課題の解決が必要

    • 言語仕様の安定化
    • 他言語からの呼び出し互換性
    • 非OS環境・組み込み機器での動作
    • 100%ブランチカバレッジの実現
    • OOM時の優雅なリカバリー
    • C並みの性能
  • Rustacean (Rust愛好者)は、SQLite開発者に直接提案可能

今後の方針

  • C言語 による開発継続方針
  • Rust への移行は慎重に検討されるが、現時点で計画なし
  • 「安全な」言語の進化には期待しつつも、 信頼性と実績重視 の姿勢

Hackerたちの意見

以前の重要な議論が2つ、このページにありますね。 https://news.ycombinator.com/item?id=28278859 - 2021年8月 https://news.ycombinator.com/item?id=16585120 - 2018年3月

tptacekのコメントが気になるな(https://news.ycombinator.com/item?id=28279426)。「このページの「セキュリティ」に関する段落は、他の議論を損なっている。実際、CはSQLiteにとって明らかなセキュリティリスクだ。」現在のドキュメントにはセキュリティに関する段落も「セキュリティ」という言葉すら一度もない。2021年版のドキュメントには、今はもう見当たらないこの文が含まれていた:「安全な言語は、セキュリティの脆弱性を防ぐのに役立つとしばしば謳われる。確かにそうだけど、SQLiteは特にセキュリティに敏感なライブラリではない。アプリケーションが信頼できないSQLを実行しているなら、すでにもっと大きなセキュリティ問題(SQLインジェクション)があるし、どんな「安全な」言語でもそれを解決することはできない。アプリケーションが信頼できないソースから完全なバイナリSQLiteデータベースファイルをインポートすることがあるのは事実だけど、そのようなインポートは攻撃ベクトルになる可能性がある。しかし、SQLiteのそのコードパスは限られていて、非常によくテストされている。そして、信頼できないデータベースを読み取ることを望むアプリケーションには、使用前に可能な攻撃を検出するのに役立つ事前検証ルーチンが用意されている。」 https://web.archive.org/web/20210825025834/https%3A//www.sql...

安全なプログラミング言語は、配列のアクセスが範囲内であることを確認するために、追加のマシン分岐を挿入します。正しいコードでは、これらの分岐は決して実行されません。つまり、マシンコードは100%分岐テストされることはなく、これはSQLiteの品質戦略の重要な要素です。ふむ、こんな新しい議論を聞くのは珍しいね。シェアしてくれてありがとう。

その分岐は、範囲外に出ることが証明されていないから、そもそも存在しないことが多いと思います。コンパイラが範囲を破ることができないことを知っていることを確実にする方法があります。

この問題は、無条件でパニックに至る分岐のカバレッジを要求しないことで軽減できるのかな?それとも、正しいコードでは決して発生しないことを示すマークをその分岐に付けることができるのかな?

つまり、安全な言語が生成するのは、例えばこういうこと? // 疑似コード if (i >= array_length) panic("インデックスが範囲外") 実際にコードが正しければ、これは決して実行されない?でも(私が正しく理解していれば)これはコンパイラによって暗黙的に追加されたチェックだよね。だから、異議はこの自動生成されたコードの正しさを疑うことに帰着するし、コンパイラの正しさを信頼していないことが前提になってる?でも、Rustコンパイラ自体はこれらのチェックが機能することを徹底的にテストしているはずだよね?もし私が議論を誤解していたら、誰か訂正してほしいな。

これは、ほとんどの人やプロジェクトからは受け入れられないような議論だけど、ヒップ博士からのは別だし、SQLiteも普通のプロジェクトじゃないからね。

安全だとわかっているなら、get_unchecked()みたいなメソッドを使って境界チェックを避けられない?

その議論にはあまり説得力を感じないな。もしコードのブランチが絶対に取られないと確信しているなら、それをテストする必要もないと自信を持つべきだと思う。これは安全性を犠牲にして無理に100%のテストカバレッジを追い求めているように感じる。チェックを省いたからといってコードの質が向上するわけじゃないし、テストカバレッジが上がるだけなんだよね。

考えるのは面白いけど(ページ全体がよく考えられてるね)、その主張は検証に耐えないと思う。自動的な境界チェックが失敗したら、プログラムはその分岐なしで未定義の動作を示してしまうし、未定義の動作(UB)は、何かしらの明確な処理をする到達不能な分岐よりも厄介だよね。Cでのシンプルな配列アクセス:arr[i] = 123; は、こう考えられるかも:if (i >= array_length) UB(); else arr[i] = 123; ここで「UB」関数は何でもできるからね。ソフトウェアの徹底的なテストと形式的な検証の観点からは、安全な言語の同等のものが欲しいな:if (i >= array_length) panic(); else arr[i] = 123; ...だって、少なくとも到達不能な条件が発生した場合に何が起こるか考えられるから。ヒップ博士は「SQLiteをGoで再コーディングするのは難しい、なぜならGoはassert()が嫌いだから」と言ってるけど、これはSQLiteが到達不能な条件を守るためにassert文を使っていることを示唆してるよね。彼のテストインフラは、到達不能なassert分岐を免除する方法があるはずなのに、なぜ境界チェック(未定義の動作が発生しないことを確認するだけのもの)が同じように扱えないんだろう?

「間違ったコードでは分岐が発生するけど、分岐のないコードは予測できない動作をするだけなんだ。シートベルトみたいなもんだよ。例えば、4ブロック運転してからシートベルトが必要になるケースがあったらどうする?まあ、そのための明確なテストはあるけど、全てをテストすることはできない。4ブロック運転して右折して、半ブロック後に何かにぶつかる場合をテストしてないし。もういいや、シートベルトを外して、シートベルトがちゃんと機能するかどうか不確実なこの狂った未テストの領域をなくそう!」

新しい理由は、全く意味がないからだよ。Cでは、配列アクセスごとに暗黙の「ブランチ」があるんだ。それはアクセス違反と呼ばれている。コードベースのすべての配列アクセスでセグフォルトをテストしてるの?してない?じゃあ、実際には100%のブランチカバレッジはないってことだね。

とはいえ、SQLiteがいつかRustで再コーディングされる可能性はあります。Goで再コーディングするのは難しそうだけど、Rustは可能性があるかも。SQLiteをRustで再コーディングする前に必要な前提条件は以下の通りです:

  • Rustはもう少し成熟して、急激な変化をやめて、古くて退屈な方向に進む必要があります。
  • Rustは、他のすべてのプログラミング言語から呼び出せる汎用ライブラリを作成できることを示す必要があります。
  • Rustは、オペレーティングシステムがないような、マイナーな組み込みデバイスでも動作するオブジェクトコードを生成できることを示す必要があります。
  • Rustは、コンパイルされたバイナリの100%分岐カバレッジテストを可能にするツールを整備する必要があります。
  • Rustは、OOMエラーから優雅に回復するメカニズムを必要とします。
  • Rustは、SQLite内でCが行うような作業を、速度のペナルティなしで行えることを示す必要があります。
  1. Rustは1.0から10年が経過しました。後方互換性のある形で変化しています。ある人たちは全く変化を望まないので、どの意味で言っているのかを明確にすることが重要です。
  2. これは実証されています。
  3. これは「マイナー」の定義によりますが、「オペレーティングシステムなし」の部分は明確に実証されています。
  4. 私は専門家ではありませんが、バイナリをテストしているので、Rust特有のものが何かは分かりません。Ferroceneの人たちがこの作業をいくつか行ったことは知っていますが、現在の状況は分かりません。
  5. Rust自体はメモリの割り当てを行いません。このOOMの挙動は標準ライブラリによるもので、これらの組み込みケースでは使用していません。そこでは、すべてライブラリコードなので、好きなようにできます。
  6. これも多くの定義に依存するので、どちらとも言えるかもしれません。

Goで`if condition { panic(err) }'をアサートの代わりに使えないのはなんで?

「安全なプログラミング言語は、SQLiteが存在していた最初の10年間には存在しませんでした。SQLiteはGoやRustで再コーディングできるかもしれませんが、そうすると修正されるバグよりも新たにバグが増える可能性が高く、コードが遅くなるかもしれません。」現代の言語は、プログラマーがバグのあるコードを書くのを防ぐためにC以上のことができるかもしれませんが、もしすでに膨大な時間と注意、テストをかけてバグのないコードがあるなら、変更の頻度が低い(またはゼロ)場合、言語が何であれあまり関係ありません。SQLiteはアセンブリ言語でも問題ないでしょう。

そして変更の頻度は低い(またはゼロ) これは、去年Googleのセキュリティブログが言っていたことと一致するね。「[メモリ安全性]の問題は新しいコードに圧倒的に多い...コードは成熟して時間と共に安全になる。」

もうGoにSQLiteのポートがあるよ :) https://gitlab.com/cznic/sqlite

「SQLiteはGoやRustで再コーディングできるけど、そうすると修正されるバグよりも新たにバグが増える可能性が高いし、コードが遅くなるかもしれない。」どうなるか見てみよう。Rust側にはかなり活発なTursoがあるよ。 https://turso.tech/

SQLiteが開発されたときにCが最適な選択だった歴史的理由や、今の利点を超えて、他の言語でSQLiteを再実装する理由は全くないと思う。軽量SQLデータベースの実装は一つだけである必要はないし、今すぐにでもRustやC++、Go、Lispなどで自分の実装を始められるよ!SQLiteの代わりに使える互換APIを作ることだってできるし、誰にも止められない!許可なんていらない!でも、どうしてわざわざ完璧に機能しているCの実装を捨てる必要があるの?25年間SQLiteを丁寧にメンテナンスしてきたCの専門家たちが、新しい言語を学んで最初からやり直すことを期待するのはどうして?

これありがとう、完全に同意するよ。最近の傾向でイライラするのは、5年以上前のものを軽蔑して、全く関係ない、時代遅れだと見ることだね。年を取っただけかもしれないけど、技術は信頼できて退屈なものが好きなんだ、特にソフトウェアは。私たちが当たり前に思っていることに何十年もの専門知識が注がれていることを尊重する人がいるのを見るのは嬉しいよ。

一つの良い理由は、ゴーランのアダプターが書かれているから、cgoなしでsqliteデータベースを使えるってことだね。あなたが言ってることには同意するよ。「sqlite」はある程度普及していて、単一の実装を超えて進化しているんだ。もちろん、Cライブラリのsqliteもあるけど、sqliteのデータベースファイルフォーマットもあって、ゴーランでsqliteの実装があっても全然おかしくない(実際、すでにあるし)し、純粋なRustの実装もできると思う。将来的には純粋なRustの実装も出てくるだろうし、もっと遠い未来にはそれが主流の実装になるかもしれないね。

実際、そういう実装は存在するよ。少なくともRustにはrqliteとtursoがあるしね。

でも、なんで完璧なCの実装を捨てる必要があるの?それに、25年間SQLiteを丁寧にメンテナンスしてきたCの専門家たちが、新しい言語を学んで最初からやり直すことを期待するの?言語の提唱が、他の人に自分のやりたいことを押し付けるだけになってしまっているからだよ。言語の採用はゼロサムゲームみたいなもので、プロジェクト'x'を言語'y'で開発しているなら、定義上言語'z'では開発していないことになる。これが言語'z'の地位を下げて、プロジェクト'x'が言語'z'で書かれていなくても続いていると、言語'z'が本当に必要なのか疑問に思わせるんだ。しかも、もし'x'の著者がどの言語で書くかを再検討したら、言語'z'だけじゃなくて、言語'd'、'l'、'j'、'g'も候補に上がるだろうね。

「SQLiteがCで書かれている理由...」はsqlite.orgに記載されている説明だね。「SQLiteがCで書かれているのはなぜで、Rustではないの?」は質問で、すぐに「Rustで書かれたSQLiteが必要なのはなぜ?」と聞きたくなる。

タイトルが編集されているから。

確かに。SQLiteがCで書かれていて、BASICじゃないのはなんでだろう?

ちなみに、そんなプロジェクトがあるよ:https://github.com/tursodatabase/turso 彼らのブログには「なぜ」の答えがヒントとして書かれてる:https://turso.tech/blog/introducing-limbo-a-complete-rewrite...

コアはメモリを割り当てないみたいだし、拡張ライブラリは安全なパターンを使って限られた場所で割り当てるみたい。だから、Rustを使ってもあまりメリットはないと思う。SQLiteは本番リリースでメモリリークや削除後の使用バグがあったことはある?もしあったなら、その質問には答えがあるけど、聞いたことがないな。それに、ダブルリンクリストやグラフは使ってるの?それらは、Rustが自分で仮想ポインタのアリーナを作らせるから、Cではある意味安全かもしれない。

Rustのメモリ安全性の保証は、ヒープ割り当てに限定されないよ。実際、言語自体はヒープ割り当てを全くしない。希望があれば、Cと同じようにリンクリストを書くこともできる。

それに、ダブルリンクリストやグラフは使ってるの?それらは、Rustが自分で仮想ポインタのアリーナを作らせるから、Cではある意味安全かもしれない。Rustでも生ポインタといくつかのunsafeコードを使えば、Cと同じようにリンクリストを実装できるよ。実際、標準ライブラリにもそれがあるし。

「SQLiteは本番リリースでメモリリークや削除後の使用バグがあったことがあるの?もちろん、古いライブラリだから、ほとんど何でもあるよ(彼らが何をしているかわからないからじゃなくて、いろいろ起こるから)。最近のCVEを見てみよう:- CVE-2025-29088 タイプ混乱 - CVE-2025-29087 範囲外書き込み - CVE-2025-7458 整数オーバーフロー、最適化されたRustでは可能だけど、テストビルドではチェックされてる - CVE-2025-6965 メモリ破損、Rustが助けにならなかったかも - CVE-2025-3277 整数オーバーフロー、Rustが助けになったかも - CVE-2024-0232 使用後解放 - CVE-2023-36191 セグメンテーション違反、Rustが助けになったか不明 - CVE-2023-7104 バッファオーバーフロー - CVE-2022-46908 検証ロジックエラー - CVE-2022-35737 配列境界オーバーフロー - CVE-2021-45346 メモリリーク... 見ての通り、sqliteのCVEの大半はRustでは起こりにくい(でも、Rustのsqlite実装はunsafeを使う可能性が高いから、全く不可能ではない)。ちなみに、2025年にこんなに多くのCVEがあるのは、Googleみたいな会社がSQLiteのファズテストをかなりやっているからかもしれない。他のポイントとしては、- 100%の分岐カバレッジは素晴らしいけど、Cではメモリの健全性を保証しない - SQLiteのCVEを深く探る人がいるおかげで、見た目ほど悪くない。最後に一つ質問:SQLiteは最高のCプログラマーを使っているけど、彼らがコードにマージするのは限られた変更だけで、典型的な会社プロジェクトに比べて変更の度合いは非常に少ない。それでもメモリの脆弱性がある。新しいプロジェクトにCを使う理由は何なんだ?」

なんか悲しいよね。彼らの主張は一見正しいように見えるけど、よく考えるとボロボロになる。2025年にCを擁護する理由って何?2000年のCを擁護すればいいのに、古くて安定した、深くテストされたCコードベースがあって、「メモリ安全性の問題がよくある」なんてことはないって主張するのは無理がある。少人数の熟練したCプログラマーがメンテナンスしてるってだけで、その主張だけで勝ちだよ。シンプルでわかりやすくて、反論しにくい。だけど、他の主張は細かく分析できて、半分は真実に過ぎない。

あなたの提案する主張は、古いコードベースのメンテナンスモードを正当化するためだけに機能する。新しい開発者をC++やRustみたいな複雑なものや、Javaのようなガーベジコレクションの遅い言語から引き離して、Cという比較的シンプルで普及している言語を考えさせたいなら、もっと何かを提供しないと。

(2017年のものだよ)

「でも、彼らが挙げる他の主張は細かく分析できて、半分は真実に過ぎない」他の主張を分析するのを見てみたいな。

コードを書く量が増え、ソフトウェアを使い、リライトについて読むにつれて... リライトに対する最大の不満は... 多くの場合、機能の均衡を取るためにリライトすることだよね。全く同じものではない。だから、途中で追加されたニッチな理由やその他の理由によるエッジケースやパッチを無視したり、忘れたりしているんだ。これが壊れたソフトウェアを意味する。以前は動いていたものが、もう動かなくなる。彼らはまた野生でそれらすべてに遭遇して、再び修正しなければならない。もちろん、こんな重要なソフトウェアをリライトするなら、もっと強調すべきことがあるけど、100%になるかどうかは理解しにくい。でも、sqlite以外にもSDLを考えてみて。もしリライトするなら、それが影響を与えないとは思えない。悪いリリースがあってから良くなるんじゃないかと予想してる。ユーザーは以前は動いていたものについて文句を言うだろう。Cは次のRustが出た後もずっと残ると思うし、たとえRustがまだ存在しても、その時には新しいRustが出てるだろう。だから、リライトする理由は何なんだ?リライトはデフォルトの考え方であるべきじゃない?

おお、Rustにそんなに高い評価があるとは思わなかった。冗談じゃないよ。