PyO3 を使った Python 内での Rust ライブラリの実行

2026/09/14 0:24

PyO3 を使った Python 内での Rust ライブラリの実行

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

本テキストは、Rust の大規模なデータ構造を完全な Python オブジェクトに変換する行為が、PyO3 を通じて JSON パーサーを実装する際に深刻なパフォーマンスボトルネックを引き起こすことを論じています。単純なチュートリアルではスカラー値を返すだけであっても、複雑な JSON ツリーを扱うためには Rust の列挙型を

IntoPyObject
トrait 経由でネイティブの Python オブジェクトとして曝すことが必要であり、この変換は意外にコストが高く、大規模ドキュメントの処理においては、解析そのものよりも総実行時間に大きく影響します。pydantic-core のようなフレームワークもこの境界面で同様のトレードオフに直面しています。これを解決するには、過剰な Python オブジェクトを蓄積するのではなく、遅延評価に基づく Rust によって支えられたビューを採用することで、完全な材料化(フルマテリアライゼーション)を避けることに優先度を置く必要があります。このアーキテクチャ的転換は、型安全性と Python との相互運用性を両立しようとする高性能ライブラリにとって不可欠であり、速度を犠牲にせずに堅牢な Rust ベンディングを統合するための明確な業界標準のモデルを確立します。

本文

PyO3 で Rust JSON パーサーを Python にエクスポートする

Pydantic v2 などのデータ検証ライブラリの背後には、Rust の拡張機能が実際に重い計算を担当しています。特に

pydantic-core
PyO3 を用いて構築されており、同様の橋渡し(バインド)を構築する方法について解説します。

本稿のゴールは、Rust で記述した JSON パーサーを Python にエクスポートし、通常のパッケージとして

import
できるようにすることです。 Rust の結果を Python オブジェクトに変換するステップは、移植を行う前に理解すべき最も重要なポイントであり、変換コストがパースコストを上回る場合さえあります。


Rust から Import までの 4 つのステップ

Rust コードを Python に取り込むには、以下の手順が必要です。

  1. Rust モジュールを作成する。
  2. PyO3
    マクロで関数やモジュールに**注釈(アノテーション)**を付ける。
    • #[pyfunction]
      :Python から呼び出すための「接着剤」として型変換と参照カウントを処理します。
    • #[pymodule]
      :PyO3 モジュールとしてパッケージ化する際にも使われます。
  3. maturin
    を使ってコンパイル
    し、インストールさせる。
    • クレートを共有ライブラリ(
      .so
      ,
      .dylib
      ,
      .dll
      など)に変換し、Python 仮想環境に配置します。
  4. Python から import する

詳細なセットアップ手順については、「PyO3 と maturin を使って Python で Rust を動かす方法」を参照してください。


パーサーがまず Rust 値を生成する

このパーサーは、

json
モジュールよりも高速な処理能力を持っており、学生チームが開発したゼロから書き込まれた JSON パーサーの実走例です(serde を使用せず、手動トークナイザと降下再帰型パーサーを採用)。

返り値の構造:
JsonValue

このパーサーは単一の Rust enum を返します。Rust の enum は多形態性を持ち、JSON ツリーときれいにマッピングできます。

pub enum JsonValue {
    Null,
    Boolean(bool),
    Number(f64),
    String(String),
    Array(Vec<JsonValue>),
    Object(HashMap<String, JsonValue>),
}
  • このツリーは完全に Rust 側で存在します。
  • Python にはその姿が届きません。PyO3 の層はこのツリーに対する薄いアダプター(変換器)の役割を果たすに過ぎません。

一つの関数を公開する

Python に関数を公開するには、以下の 2 行のコードがすべてを網羅します。

#[pyfunction]
fn parse_json<'py>(py: Python<'py>, input: &str) -> PyResult<Bound<'py, PyAny>> {
    parse(input)?.into_pyobject(py)
}

関数署名のポイント

  • py: Python<'py>
    • Python インターpreter アクセスを表すトークンです。
    • PyO3 API で Python オブジェクトを扱う際に必須で、従来のビルドでは GIL(グローバルインターロッキングロック)の管理と関連付けられます。
  • Bound<'py, PyAny>
    • 任意の型の Python オブジェクトへのハンドルです。Rust 側の
      PyObject
      に相当します。
  • PyResult<T>
    • Result<T, PyErr>
      の型です。正常時は値を返し、エラー時には PyO3 が処理した Python 例外を返します。
    • ?
      オペレーターにより、パース失敗時に早期終了し Python 側で例外として認識されます。

処理の流れは以下の通りです:

  1. parse(input)?
    :実際のパース処理を実行。
  2. .into_pyobject(py)
    :呼び元が要求する Python オブジェクトを構築する。
    • 注意: この最後の呼出が最もコストがかかります。ツリーノードに対して Python オブジェクトを作成する作業量は、入力によってはパース自体の作業量を上回ることがあります。

帰りの旅は高価です:変換コストについて

なぜこの変換が無料ではないのか?

.into_pyobject
メソッドは、
JsonValue
ツリー全体を歩き回り、それをネイティブな Python オブジェクトとして再構築します(dict, list, float/str など)。これは
IntoPyObject
トrait を実装することで提供されます。

変換ロジックの実装例

impl<'py> IntoPyObject<'py> for JsonValue {
    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        match self {
            JsonValue::Null => Ok(py.None().into_bound(py)),
            
            // 数値の処理
            JsonValue::Number(n) => {
                Ok(n.into_pyobject(py)?.to_owned().into_any())
            },

            // オブジェクト (Map) の処理
            JsonValue::Object(obj) => {
                let py_dict = PyDict::new(py);
                for (k, v) in obj {
                    py_dict.set_item(k, v.into_pyobject(py)?)?; // 再帰呼び出し
                }
                Ok(py_dict.into_any())
            },

            // ... 配列、文字列、ブール値も同様に処理

        }
    }
}

メリットとデメリットの比較

規模パース完了後メリット・デメリット
小さなデータノード数少コストは軽微で問題なし
大きなドキュメント10 万件の Python オブジェクト作成変換プロセスがエンドツーエンド時間の支配要因になる可能性あり

特に大きなファイルの場合、パース自体よりも「マテリアライゼーション(具現化)」ループに時間がかかっているケースがあります。


エラーも同様に境界を横切ります

戻り値だけでなく、エラー処理も境界面で翻訳する必要があります。パース失敗は Rust 側では型付きエラーですが、Python 側では例外が必要です。

From
トrait を実装し、
?
オペレーターに任せることで自動的に変換されます。

impl From<JsonError> for PyErr {
    fn from(err: JsonError) -> PyErr {
        match err {
            // 文字列未終了エラーの例
            JsonError::UnterminatedString { position } => PyValueError::new_err(
                format!("Unterminated string starting at position {position}")
            ),
            // ... 他のエラーバリエーションも同様に処理
        }
    }
}
  • 不正な入力が入力位置付きの
    ValueError
    として投げられます。
  • ファイル読み取りエラー (
    std::io::Error
    ) も自動的に対応する Python 例外(例:不存在の場合は
    FileNotFoundError
    )に変換されます。
  • これにより、呼び出し元は Python のセマンティクスを得られ、Rust 側の詳細が漏れ出すことはありません。

あなたが自前のポートに意味することを考える場合

移植する Rust 関数の返り値によって戦略を変えましょう。

1. スカラーを返す場合

  • 単一の値(数値、文字列など)を返すなら、単純な移植で十分です。
  • 境界面のオーバーヘッドは通常無視できます。

2. 大きな構造体を返す場合(重要)

  • 巨大なツリー全体を Python オブジェクトに「具現化」するコストが実際の問題となります。
  • パース自体が高速化した後、最適化するべきはこの変換プロセスです。

アーキテクチャ的アプローチ:遅延評価

限界を超えるためのより大きな勝利はアーキテクチャの変更にあります。

  • ツリー全体を具現化せず、呼び出し元がアクセスする部分のみを変換します。
  • Rust によるバックエンドを持つビューを提供し、必要な時にのみ Python オブジェクトを構築してください(遅延評価)。

結論: PyO3 を採用する際は、アルゴリズムだけでなく**「境界面のプロファイリング」**も行いましょう。Rust が高速でも、「帰りの道」(変換ステップ)が遅いと移植の恩恵は薄れます。

同じ日のほかのニュース

一覧に戻る →

2026/09/14 6:06

Claude Fable 5.1 が、370年もの間解読されてこなかったシフラル・ディスティッヒを解読しました

## Japanese Translation: Claude AI が、ロイヤリストのトマス・アークハート(*Logopandecteision* および *The Jewel* の作品)から提示された 2 つの歴史的に未解決のカギ合を成功裏に解読し、ブルートフォース計算や人類による事前の解読なしに、王チャールズ 2 世への隠された祈りを明らかにしました。これらのカギ合は、既知の解法が存在せず、CIA などの組織によってこれまで追求されたことがなかったため、特に選ばれました。大規模なデータ処理ではなく、モデルは単純で埋め込まれた構造的ロジックを認識しました:一方のカギ合は、各数字をアークハートの 32 の「Proquiritations」内の単語インデックスにマッピングし、他方は *The Jewel* のページインデックスに数字をマッピングし、それらのページの最初の一語の頭文字を採用します。これにより、「O GOD UPHOLD KING CHARLS THE SECOND / MAKE HIM THE SUPREME RULER OF THIS LAND」という 2 つのロイヤリストの祈りと、「GREAT LORD, MANTAINE THAT REGAL FAMILIE / WHEREOF KING CHARLS THE SECOND IS THE HEAD...」という ottava rima 形式の祈りが得られ、写本エラー、ハイフン接続語、ページシフトオフセット、および *The Jewel* の不読み可能なセグメントによる軽微な不一致を除いて正確です。検証の結果、275 の位置のうち 231 が正確な最初の単語の一致を示しています。1652 年版の *Jewel* のフリーデジタル画像が存在しないため、残りの不明点を解決するには実物コピーまたはジャック&ライアルの 1983 年版が必要です。この成果は、高度な AI が以前見過ごされてきた微妙な構造的パターンを検出することで歴史的真実を明らかにすることを示しており、暗号解析を計算的なブルートフォースからパターン認識へ転換しました。

2026/09/14 2:37

Google はなぜ依然として不適切な広告を表示し続けているのでしょうか?

## Japanese Translation: Google の高度な AI モデルである Gemini は、iOS システムアラートのパロディを用いてユーザーをクリックさせるよう誘導する欺瞞的な YouTube 広告を特定しました。オペレーティングシステムのダイアログを模倣した広告を禁止する厳格なポリシーが存在にもかかわらず、この誤解を招くクリエイティブは複数回のユーザー苦情にもかかわらず人間による審査官によって以前承認されていました。広告は非機能のボタンを用いて緊急のハードウェア故障状態を偽造し、視聴者にデバイスが直ちに技術的危機に直面しているという錯覚を成功裡に抱かせました。この操作は虚偽表示に関する基本的なルールに違反し、プラットフォームの安全メカニズムに対するユーザーの信頼を損ないます。したがって、Google はクリエイティブコンテンツを即座に承認停止するよう推奨し、ポリシー違反警告を発出することを示唆しています。広告主がこの種の欺瞞的な実践を継続した場合、アカウントの完全な停止のリスクに直面します。この事例は、人間による監視と自動検知の間にある重大なギャップを浮き彫りにしており、Google はこれらの洗練された詐欺を特定できる強力な AI を保有していますが、システムはまだ有害コンテンツがユーザーに到達する前に能動的にブロックするためにそれらを完全に活用していないという状況です。

2026/09/10 21:27

Julia 1.13 のハイライト

## Japanese Translation: Julia 1.13 がリリースされ、回帰と課題を特定することに焦点を当てたテスターおよびコントリビューターからの大きな貢献が反映されています。今回のアップデートは、特に起動時間とパッケージの前コンパイルにおいて劇的なパフォーマンス向上をもたらします。ベンチマークによると、パッケージの読み込みはバージョン 1.12 に比べて約 30% 速く(LTS の 1.10 に比べて約 10-20% 速く)、アプリケーションの起動時間は 1.12 に比べて約 20% 向上しており、平均的なスピードアップ率は約 1.22 倍です。これらの改善は、AbstractString および数値型に対して RapidhashNano を採用したことであり、イメージオブジェクトのマーキングをスキップしてフルコレクション時間を短縮した強化された garbage collection、そして新しいデフォルトのハッシュングアルゴリズムという技術的なアップデートによって実現されています。より迅速な開発ワークフローを支援するために、重要なバグ修正により Ctrl-C を通じた割り込み処理がより信頼性高く、タスクのカANCEL mechanisms が改善されました。また、リリースには REPL に直接組み込まれる貴重な開発者ツールが含まれます:内部実装による構文ハイライトは OhMyREPL.jl などの外部パッケージの必要性を排除し、新しい fzf スタイルの履歴検索(Ctrl-R)がファジー検索と複数結果の選択、そして REPL モードの表示をサポートします。また、Windows では効率的なテキスト入力を可能にする括弧付きペースト機能も利用可能です。診断機能をさらに強化するために、「--trace-eval」フラグにより、テストスイートやスクリプトでの停滞を特定しながらトップレベルの評価進捗を監視することができ、新しい「@__FUNCTION__」マクロは「#self#」の代替としてパブリック API として機能します。さらに、イントロスペクションマクロは型の付いた呼び出し式を受け付けるようになり、Time To First X(TTFX)モニタリングは 2026 年 9 月 7 日より稼働開始される新しい CI ジョブを通じて Julia の開発プロセスの一部として統合されました。これらの改善は、テスト時や大規模スクリプト実行時の待ち時間を大幅に削減し、個人のコントリビューターおよびエンタープライズチームの両方に対して全体の生産性を高め、より速いフィードバックループを提供します。 ## Text to translate: Julia version 1.13 has been released with significant contributions from testers and contributors focused on identifying regressions and issues. The update delivers dramatic performance improvements, particularly in startup times and package precompilation. Benchmarks indicate that loading packages is now roughly 30% faster than in version 1.12 (and roughly 10-20% faster than 1.10 LTS), while application startup times have improved by approximately 20% over 1.12, with a mean speedup of ~1.22x. These gains are driven by technical updates including the adoption of RapidhashNano for AbstractString and numeric types, enhanced garbage collection that skips marking image objects to reduce full collection time, and new default hashing algorithms. To support this faster development workflow, critical bug fixes ensure more reliable interrupt handling via Ctrl-C and improved task cancellation mechanisms. The release also introduces valuable developer tools directly into the REPL: built-in syntax highlighting replaces the need for external packages like OhMyREPL.jl, a new fzf-style history search (Ctrl-R) supports fuzzy searching with multiple result selection and REPL mode indication, and bracketed paste functionality is now available on Windows for efficient text input. Further enhancing diagnostics, the `--trace-eval` flag allows users to monitor top-level evaluation progress to identify hangs in test suites or scripts, while a new `@__FUNCTION__` macro serves as a public API alternative to `#self#`. Additionally, introspection macros now accept call expressions with types, and Time To First X (TTFX) monitoring is now an integrated part of Julia's development process through new CI jobs, going live on September 7, 2026. Collectively, these improvements significantly reduce wait times during testing or large-scale script execution, thereby boosting overall productivity and providing faster feedback loops for both individual contributors and enterprise teams.