以下の内容はhttps://nikkie-ftnext.hatenablog.com/entry/rust-maturin-cli-and-import-support-python-libraryより取得しました。


Rust プログラムから作った Python パッケージで、エントリポイントスクリプトも import もサポートするには

はじめに

七尾百合子さん、お誕生日 186日目 おめでとうございます! nikkieです。

Rust プログラムをバイナリにして Python パッケージとして配布してきましたが、Python で import もできるようにする方法を調べました。

目次

前回の sampleproject-rs!

https://pypi.org/project/sampleproject-rs/0.1.0/

Rust プログラムから作ったバイナリは、maturin で Python パッケージにできます!

pyproject.toml のこの指定がポイントです:

[tool.maturin]
bindings = "bin"

Linux, macOS, Windows 各環境向けにビルドし、PyPI で配布しています1

% uvx --python 3.13 --from sampleproject-rs sample
Call your main application code here

sampleproject はsampleコマンドも import もできる

sampleproject-rs は Pythonsampleproject を Rust で実現しようと始めました。
本家 sampleproject はsampleコマンドだけでなく、import もできます

% uvx --python 3.13 --from sampleproject sample
Call your main application code here
% uv run --python 3.13 --with sampleproject python
>>> from sample.simple import add_one
>> add_one(20)
21

sampleproject-rs を sample コマンドだけでなく、Python で import もできるようにするにはどうしたらよいのでしょう?

ドキュメント「Both binary and library?」

Maturin User Guide に見つけました。

その中のBoth binary and library?

Consider instead exposing a CLI function in the library and using a Python entrypoint:

  • PyO3 を使って、Rust で書いたライブラリを Python からモジュールとして import できるようにする
  • Python パッケージの metadata の project.scriptsエントリポイントスクリプトを実現する

PyO3 で sample ライブラリ爆誕

pyo3.rs

Declarative modulesを使いました2

lib.rs

use pyo3::prelude::*;

#[pyfunction]
fn main() {
    println!("Call your main application code here");
}

#[pyfunction]
fn add_one(number: i32) -> i32 {
    number + 1
}

#[pymodule]
mod sample {
    #[pymodule_export]
    use super::main;

    #[pymodule_export]
    use super::add_one;
}

Python 処理系から見て sample モジュールは mainadd_one という2つの関数を持ちます。
main 関数をエントリポイントスクリプトに指定します。

[project.scripts]
sample = "sample:main"

全容はこちらから:

ローカルで動作確認する例

(.venv) % python -V
Python 3.13.0
(.venv) % uvx maturin develop
(.venv) % python -c 'from sample import add_one; print(add_one(20))'
21
(.venv) % sample
Call your main application code here

sampleコマンドの実体は、バイナリではなくテキストファイルスクリプト)です。
これは純正 Python パッケージの場合と同じですね。

% cat .venv/bin/sample
#!/.../sampleproject-rs/.venv/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from sample import main
if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

宿題事項

  • sample.simple.add_oneのようにネストさせられる?
    • 試しにmainadd_oneも simple の下に置いてみたらコンパイルエラー
  • main.rsを残しているが、lib.rsにあるmain()関数を呼ぶようにできる?(DRYにする意図)

終わりに

Rust プログラムから作ったバイナリを maturin で Python パッケージにするだけでなく、PyO3 からモジュールにまとめ、Python で import して使う方法を知りました。
後者のアプローチでは、エントリポイントスクリプトを使うことで、Rust プログラムを Python 環境で import もコマンド実行もできます!


直近のLTより
https://ftnext.github.io/2025-slides/python-fukuoka/why-we-can-run-rust-package-cli#/4


  1. 2025年9月17日(水)のリリース - nikkie-ftnextの日記
  2. 積ん読していた記事は PyO3 の変更で古くなってしまっていました



以上の内容はhttps://nikkie-ftnext.hatenablog.com/entry/rust-maturin-cli-and-import-support-python-libraryより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14