Pythonから自作Rustプログラムを実行する方法
この記事ではRustで書いた高速なロジックをPythonから呼び出す方法と、
「混合ライブラリ」としてのベストプラクティスな構成を解説します。
最終的なディレクトリ構成
まず最初に、ゴールとなるディレクトリ構成を書いちゃいましょう。
プロジェクトの全体像は以下のようになります。
例として、振り子の物理シミュレーションをするときにフォルダの名前にしています。
rust-python-tutorial/
├── Cargo.toml # Rustのビルド設定(lib名に _ を付ける)
├── pyproject.toml # Pythonのビルド設定(maturinを使用)
├── src/
│ └── lib.rs # Rustソースコード(#[pymodule]名に _ を付ける)
├── python/
│ ├── pendulum_lib/ # Pythonパッケージフォルダ
│ │ └── __init__.py # Rustモジュールを読み込んで公開する
│ └── run_example.py # 実行サンプル
└── venv/ # 仮想環境Step 1: プロジェクトの準備
Cargo.toml
まず、以下を実行してRust側の初期化を行います。
cargo init --libその後、Python連携用に必要な設定をCargo.toml に追加します。
サンプルは以下の通りです。
[package]
name = "pendulum_lib"
version = "0.1.0"
edition = "2024"
[lib]
name = "_pendulum_lib" # アンダースコアを付けるのがポイント
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.23", features = ["extension-module"] }[lib] nameをアンダースコア付きにしておく。(後述)crate-type = ["cdylib"]:
通常、Rustのライブラリ(rlib)はRust同士で使うための形式。しかし、PythonはC言語と同じ形式(共有ライブラリ)のファイルを読み込む仕組みになっているため、cdylib(C-Dynamic Library)を指定して、Pythonが理解できる形式で出力させる必要がある。features = ["extension-module"]:
PyO3を使用する際に必須のフラグ。これを指定することで、PyO3はPythonの拡張機能として動作するための特別なリンク設定を行う。これを付け忘れると、コンパイル時や実行時にエラーが発生する原因になる。
pyproject.toml
pyproject.toml は、Python プロジェクトのビルド方法や依存関係を定義する「設計図」です。module-name にパッケージ構造を反映させます。
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[project]
name = "pendulum_lib"
version = "0.1.0"
dependencies = [
"numpy",
"matplotlib"
# 他のライブラリはここ
]
[tool.maturin]
python-source = "python" # Pythonのフォルダを指定
module-name = "pendulum_lib._pendulum_lib" # 正しい配置先を指定[build-system]:
「このプロジェクトをインストールするときはmaturinを使ってコンパイルしてね」という道具の指定。これがあるおかげで、pip install時に自動的に Rust のコンパイルが走る。[project] dependencies:
他の Python ライブラリ(numpy や matplotlib など)を使いたい場合はここにリスト形式で書きます。ここに書かれたライブラリは、自身のプロジェクトをインストールする際に自動的に一緒にインストールされます。[tool.maturin]:
Rust と Python の橋渡し設定です。python-source: Python のソースコードがどのフォルダにあるかを指定します。module-name: コンパイルされた Rust バイナリを Python パッケージ内のどこに置くかを指定します。混合ライブラリ構成ではパッケージ名._バイナリ名とするのがベストプラクティスです。
Step 2: Rustコードを書く (src/lib.rs)
src/lib.rs にRustのコードを書きます。以下にサンプルを示します。
Pythonに公開したい要素に `#[pyo3]` 系の属性を付けておきましょう。

余計なコメント多くてごめん!
use pyo3::prelude::*;
// 1. 構造体をPythonクラスとして公開する
#[pyclass]
pub struct Params {
#[pyo3(get, set)] // Python側から obj.m1 のように読み書き可能にする
pub m1: f64,
#[pyo3(get, set)]
pub l1: f64,
#[pyo3(get, set)]
pub m2: f64,
#[pyo3(get, set)]
pub l2: f64,
#[pyo3(get, set)]
pub g: f64,
}
#[pymethods]
impl Params {
// Python側で Params(1.0, 1.0, ...) と呼べるようにするコンストラクタ
// Pythonの __init__ に相当します
#[new]
fn new(m1: f64, l1: f64, m2: f64, l2: f64, g: f64) -> Self {
Params { m1, l1, m2, l2, g }
}
}
// 2. Pythonから呼び出すメイン関数
// 引数を &Params (参照) にすることで、データのコピーを避け、
// 大規模なシミュレーションでもメモリ効率よく高速に動作します
#[pyfunction]
fn run_simulation(p: &Params, theta1: f64, theta2: f64, dt: f64, steps: usize) -> Vec<(f64, f64)> {
let mut history = Vec::with_capacity(steps);
// ... ここで物理計算を実行 ...
// Rustの Vec<(f64, f64)> は、Python側では
// 自動的にタプルのリスト [(f1, f2), ...] に変換されます
history
}
// 3. モジュールの登録
// アンダースコア付きの「隠しモジュール」として登録
#[pymodule]
fn _pendulum_lib(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Params>()?; // クラスを登録
m.add_function(wrap_pyfunction!(run_simulation, m)?)?; // 関数を登録
Ok(())
}PyO3の主要なマクロ(アトリビュート)の解説
RustのコードをPythonへ繋ぐために使用する、接着剤のようなマクロの役割を解説します。
#[pyclass]:- 役割: Rustの構造体を、Pythonの「クラス」として定義します。
- ポイント: これを付けることで、Python側でインスタンス化が可能になります。構造体のフィールドに
#[pyo3(get, set)]を付けると、Python側からその値を直接読み書きできるようになります。
#[pymethods]:- 役割:
#[pyclass]で定義した構造体に対して、Python側から呼べるメソッドを定義します。 - ポイント:
fn new(...)に#[new]を付けると、Pythonのコンストラクタ(__init__)になります。その他の関数は、Pythonのインスタンスメソッドとして公開されます。
- 役割:
#[pyfunction]:- 役割: Rustの関数を、Pythonの「関数」としてそのまま公開します。
- ポイント: 引数や戻り値の型(
f64,String,Vec,tupleなど)は、PyO3が自動的にPythonの対応する型へ変換してくれます。
モジュール登録コードの詳細解説
上記のモジュール登録部分には、PyO3の重要なエッセンスが詰まっています。
#[pymodule]:
この関数がPythonモジュールの「入り口」であることを宣言するマクロです。関数名はビルドされるバイナリ名(_pendulum_lib)と一致させる必要があります。&Bound<'_, PyModule>:
PyO3 0.21から導入された新しい標準API(Bound API)です。Bound: そのオブジェクトが「Pythonのメモリ管理(GIL)の保護下にあること」を型システムで保証します。これにより、メモリ安全性が格段に向上しました。PyModule: Pythonのモジュールオブジェクトそのものを指します。
m.add_class::<Params>()?:#[pyclass]を付けた構造体を、Python側のクラスとして登録します。これにより Python側でpendulum_lib.Params(...)と呼べるようになります。wrap_pyfunction!(run_simulation, m)?:
Rustの関数をPythonが理解できる形式にラップ(変換)して登録します。?演算子:
もし登録に失敗した場合、即座にエラーを返します。これはPython側では適切な例外(Exception)として処理されます。
Step 3: Pythonパッケージの作成
__init__.py を作成 してRustモジュールをインポート
Rustの機能を外部へ公開するために、__init__.py を作成します。
python/
└── pendulum_lib/
└── __init__.pypython/pendulum_lib/__init__.py の内容は以下の通りです。
# 裏方のRustモジュールからすべてを読み込んで公開する
from ._pendulum_lib import *from ._pendulum_lib import * の詳細解説
ここでの「すべて(*)」とは、Rust側の #[pymodule] ブロック内で登録したすべてのクラスと関数を指します。
今回の例では、以下の要素がパッケージの直下に公開されます。
m.add_class::<Params>()?で登録したParamsクラスm.add_function(wrap_pyfunction!(run_simulation, m)?)?で登録したrun_simulation関数
なぜこの一行が必要なのでしょう。
この一行がない場合、ユーザーは from pendulum_lib._pendulum_lib import Params のように、アンダースコア付きの内部モジュールを意識して深く指定しなければなりません。__init__.py で中身を表面に引き出すことで、ユーザーは from pendulum_lib import Params という自然で使いやすい形式でインポートできるようになります。
このように「中身の実体(Rustバイナリ)」を隠しつつ、「使いやすい窓口(Pythonパッケージ)」を提供するのが使いやすくておすすめです。
Step 4: ビルドと実行
ここまでできたらあとばRustコードをビルドするだけです。
まずは仮想環境を有効化しておきましょう。
そして、maturin という「ビルドツール(RustとPythonを繋ぐ橋を作る道具)」をインストールします。pip でインストールすることができます。
pip install maturin maturin がやってくれることは以下の通りです。
- Rustを特殊な設定でコンパイルする(共有ライブラリ化)。
- OS(Windows/Mac/Linux)に合わせた適切な拡張子(
.pydや.so)にする。 - Pythonの検索パスが通っている場所にファイルを置く。
- 配布する場合は、
pip installできる形式(Wheel)にパッケージングする。
インストールができたら、maturin でビルドするとPython側にパッケージとしてインストールすることができます。
maturin developあとはPython側で、
import pendulum_libとすれば使用することができます。
開発のポイント
1. なぜアンダースコアを付けるのか?
Pythonパッケージ名(フォルダ名)とRustモジュール名が全く同じだと、Pythonがどちらを読み込めばいいか混乱してしまいます。
Rust側を _ 付きの「裏方」にすることで、この衝突を防ぎ、Python側で __init__.py 使った柔軟な設計が可能になります。
2. なぜ「別名」ではなく「アンダースコア付き」なのか?
pendulum_engine のような全く別の名前にせず、あえて _ を付けるのには、Pythonの設計思想に基づいた理由があります。
Python標準ライブラリの慣習:
Pythonの標準ライブラリ(ssl や socket など)も、
高速なC言語の実装を _ssl や _socket という名前で内部に隠し、ユーザーには _ なしの使いやすいAPIを提供しています。
この慣習に従うことで、他のエンジニアが見た時に「これは内部実装用の高速化パーツだな」と直感的に理解できます。
補完を汚さない
IDEのオートコンプリートにおいて、_ で始まるモジュールは優先順位が低く設定されます。
これにより、ユーザーが import した際に「表の顔」であるパッケージだけが候補に上がり、迷わせない設計になります。
親子関係の明示
_pendulum_lib とすることで、「これは pendulum_lib パッケージ専用のコアエンジンである」という親子関係が名前だけで明確になります。
3. 混合ライブラリの拡張性
この構成なら、python/pendulum_lib/utils.py のようなファイルを追加して、Rustの高速計算とPythonの便利なライブラリを組み合わせた「最強のツールセット」を簡単に作ることができます。
まとめ
この記事ではRustで書いた高速なロジックをPythonから呼び出す方法と、
「混合ライブラリ」としてのベストプラクティスな構成を解説しました。
Pythonでは遅い処理をRustに任せると、劇的に高速化できるのでぜひトライしてみてください!
