2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RustのコマンドラインツールとRust製Pythonモジュールをプロジェクト内に共存させる

Last updated at Posted at 2025-03-10

はじめに

maturin / PyO3 を使ってPythonの拡張モジュールをRustで実装する、ことをよくしていたが、コマンドラインツールとしてもビルドする必要が出てきたので、その方法をまとめる。

要件:以下の2つを同じプロジェクトでできるようにする。

  • Rustのコマンドラインツールの実行バイナリ(cargo buildでビルド)
  • pip installできるPythonモジュール(maturin buildでビルド)

外部リンク:

Python側の環境準備

  • maturinをインストール
pip install maturin

プロジェクトの作成

単にPythonモジュールを作成する場合はmaturin new --bindings pyo3 my_projectで作成するが、今回はRustのコマンドラインツールの実行バイナリもビルドできるようにしたいので、--mixedオプションを指定する。

  • maturin new --help--mixedオプションを確認
--mixed
    Use mixed Rust/Python project layout
  • Rust/Python混合プロジェクトを新規作成
maturin new --bindings pyo3 --mixed my_project

Cargo.tomlpyproject.tomlの修正

Cargo.tomlの修正

  • [features]の追加

cargo buildでPython関連のソースをビルドしようとしてエラーと成ることを防ぐ。

[features]
default = [] # デフォルトではPython関連の機能を無効化
python = ["dep:pyo3"] # `python` フィーチャーが有効な時のみ `pyo3` を使う
  • [lib]crate-type"rlib"を追加

src/lib.rsをPython用とコマンドライン用それぞれの用途でビルドできるようにする。

[lib]
name = "my_project"
#  ["cdylib"]
crate-type = ["cdylib", "rlib"]
# "cdylib"はPython用、"rlib"はコマンドライン用
  • [dependencies]pyo3にオプション追加

ビルドソースを選んでビルドできるようにする。

[dependencies]
# pyo3 = "0.23.3"
pyo3 = { version = "0.23.3", features = ["extension-module"], optional = true }

pyproject.tomlの修正

  • [tool.maturin]features"python"を追加

maturin buildでのビルド対象の選択にpythonフィーチャーを使えるようにする。

# features = ["pyo3/extension-module"]
features = ["pyo3/extension-module, python"]

ソースの修正

src/lib.rsの修正

  • コマンドラインでも使いたいソースと、Python用のみに使いたいソースを分ける。
  • cargo buildでビルドしたくないPython用のソースには#[cfg(feature = "python")]を付ける
#[cfg(feature = "python")]
use pyo3::prelude::*;

pub fn sum_as_string_rs(a: usize, b: usize) -> String {
    (a + b).to_string()
}

#[cfg(feature = "python")]
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok(sum_as_string_rs(a, b))
}

#[cfg(feature = "python")]
#[pymodule]
fn my_project(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}

src/main.rsを追加

  • プロジェクト作成時点ではsrc/main.rsはないので追加で作成
touch src/main.rs
tree -L 3  # ディレクトリ構成の確認

ディレクトリ構成は下記のようになるはず。

.
├── Cargo.lock
├── Cargo.toml
├── pyproject.toml
├── python
│   ├── my_project
|   |   └── __init__.py
│   └── tests
│       └── test_all.py
└── src
    ├── lib.rs
    └── main.rs

src/main.rsを下記のように編集する。

use my_project::sum_as_string_rs;

fn main() {
    let text = sum_as_string_rs(1, 1);
    println!("1 + 1 = {}", text)
}

ビルド+実行+pip install

cargo run
maturin build
pip install wheel
pip install ./target/wheels/my_project-*.whl

Pythonコードの修正

import関連のコードが動かない状況なので、修正する。

python/my_project/__init__.pyの修正

# from .my_project import *
from my_project import my_project

python/tests/test_all.pyの修正

# import my_project
from my_project import my_project

この修正でpytestが動くはず。

pip install pytest
my_project $ pytest
=============================== test session starts ================================
platform darwin -- Python 3.12.3, pytest-8.3.5, pluggy-1.5.0
rootdir: /<directory>/my_project
configfile: pyproject.toml
collected 1 item                                                                   

python/tests/test_all.py .                                                   [100%]

================================ 1 passed in 0.04s ================================

さいごに

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?