S-JIS[2024-10-26] 変更履歴

Rust PyO3メモ

Rustのpyo3クレートのメモ。


概要

PyO3は、Pythonから呼べるRustの関数を作成するクレート(ライブラリー)。


PyO3は以下のような手順で使用する。

  1. Rustプロジェクトを作成する。
  2. pyo3クレートを使って(Pythonから呼ぶ為の)Rustの関数を定義する。
  3. maturinを使ってビルドする。(RustのビルドとPythonへのインストールが行われる)
  4. Pythonでimportして関数を呼び出す。

maturinはPyO3が提供しているツールで、PyO3用のビルドを行う。


Cargo.toml:

〜

[lib]
name = "py_example"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.22.5", features = ["extension-module"] }

[lib]のnameは、Pythonのimport文で指定する名前となる。
ちなみに、この名前にはハイフン「-」を使うことは出来ない。


maturinのインストール

先にPythonをインストールしておく。
maturin 1.7.4はPython 3.7以降に対応している。


maturinはpipコマンドでインストールする。

pip install maturin
> maturin --version
maturin 1.7.4

PyO3の例

まず、Rustのプロジェクトを作成する。

maturinを使ってPyO3用のRustプロジェクトを作成することも出来るらしいが、普通のRustプロジェクトでも大丈夫。


Rustプロジェクトを作ったら、Pythonのvenv環境を作っておく。(後から作っても構わない)

cd プロジェクト
python -m venv .venv

これにより、「.venv」というディレクトリーが作られる。


Rustで関数を定義する。

src/lib.rs:

use pyo3::prelude::*;
#[pyfunction]
fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
    Ok((a + b).to_string())
}

Pythonから呼び出す関数には#[pyfunction]を付ける。

そして、Pythonでimportするモジュールとなる関数を定義し、その中でPythonから呼び出す関数を登録する。

#[pymodule]
fn py_example(m: &Bound<PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    Ok(())
}

#[pymodule]が付いた関数の関数名は、Cargo.tomlの[lib]のnameと合わせる必要がある。


次に、maturinを使ってビルドする。

cd プロジェクト
maturin develop

これにより、Rustのビルドが行われ、Pythonにインストールされる。
(.venv/Lib/site-packagesにライブラリーがインストールされる)

※Pythonのvenv環境が作られていないと、ビルドが失敗する。


実行してみる。

まず、venv環境を起動する。

cd プロジェクト
.venv/Scripts/activate

venvのプロンプトが出たら、pythonコマンドを実行する。

python

そしてpyhonの関数を呼び出してみる。

>>> import py_example
>>> py_example.sum_as_string(1, 2)
'3'

バイト配列を返す例

文字列を受け取ってバイト配列を返す例。


Pythonのバイト配列はPyBytesという構造体で表す。

use pyo3::{prelude::*, types::PyBytes};
#[pyfunction]
fn to_bytes(py: Python, s: &str) -> PyResult<Py<PyBytes>> {
    let bytes = s.as_bytes();
    let py_bytes = PyBytes::new_bound(py, bytes);
    Ok(py_bytes.into())
}

PyBytesのインスタンスを作るにはPythonオブジェクト(py)が必要。
これはPythonから呼ばれる関数の第1引数で受け取るように定義する。


pymoduleの方は特に変わり無し。

#[pymodule]
fn py_example(m: &Bound<PyModule>) -> PyResult<()> {
    〜
    m.add_function(wrap_pyfunction!(to_bytes, m)?)?;
}

呼び出すPythonの側では、第1引数のpyは指定しなくていい。

>>> import py_example
>>> py_example.to_bytes("abc")
b'abc'

Rustへ戻る / 技術メモへ戻る
メールの送信先:ひしだま