はじめに
Rust, 速いし書きやすいし最高ですよね? でも普段データをプロットしたり解析したりするには Python 使ってますよね? Python から Rust の関数を呼び出せたらもっと最高ですよね?
PyO3 は Rust で Python パッケージを作成, または Python を Rust から呼び出す機能を持つクレートです. 使い方を知るには基本的には ユーザーガイド (英語) を読めば良いと思いますが, 現在の状況に基づく日本語の文献が乏しいので, 2019年7月時点の最新版 v0.7.0 に基づいて基礎的な部分を説明します.
この記事では Rust で Python パッケージを作成する方法を扱います.
準備
Rust 1.34.0-nightly 以上, および Python 3.5 以上が実行可能な環境が既に整っていると仮定します. 2019年7月現在 nightly 必須 です (この issue). なお本記事の内容は 2019年7月22日に Rust 1.38.0-nightly (2019-07-19) + Python 3.5.3 (システム)/Python 3.7.2 (自前ビルド) on Debian 9.9 および Python 3.7.4 (ストア版) on Win10 1903 で検証しました.
作成したいパッケージ名を rust2python
とします. 新しく lib クレートを作成し, Cargo.toml
に次の内容を追記します.
[lib]
name = "rust2python"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.7.0", features = [ "extension-module", ] }
型の対応関係
Rust で Python パッケージを作成する以上, Rust の型が Python の型に変換されます. これは PyO3 が自動的に遂行してくれますが, ある程度対応関係を把握しておかないとコードを書けません. そこで最初に型の対応関係についてまとめておきます.
Rust | Python |
---|---|
i32 , usize 等 |
int |
f32 , f64
|
float |
bool |
bool |
Vec<T> |
list |
String |
str |
HashMap |
dict |
つまりは普通に使う型は極めて普通に対応しているため, たいていうまくいきます. ちなみに関数の引数に入れようとして型エラーが発生した場合も普通に Python の方でエラーが発生するだけです.
なお struct/class に関することは ガイド を, numpy を扱う方法については rust-numpy を見てください.
パッケージ作成
それでは src/lib.rs
の中身を作成しましょう. まずは use pyo3::prelude::*;
により必要なものをインポートしておきます.
次に, Python から呼び出したい関数を #[pyfunction]
アトリビュート付きで実装します. 戻り値は必ず PyResult<T>
である必要があります. 戻り値のない関数を定義する場合は PyResult<()>
とすればよいです. 例えば hello world なら
#[pyfunction]
fn hello() -> PyResult<()> {
println!("Hello, world!");
Ok(())
}
引数や戻り値がある場合もごく素直に書けばよいです. 例えば円周率の 0 倍, 1 倍, ..., n-1 倍のリストをつくる関数なら
use std::f64::consts::PI;
#[pyfunction]
fn pi_times( n: usize ) -> PyResult<Vec<f64>> {
Ok(
(0..n).map(|i| i as f64 * PI).collect()
)
}
必要な関数を実装し終えたら, それを Python モジュールにまとめます. これは [#pymodule]
アトリビュート付きの関数として実現されます.
use pyo3::wrap_pyfunction;
#[pymodule]
fn rust2python(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!( hello ))?;
m.add_wrapped(wrap_pyfunction!( pi_times ))?;
Ok(())
}
これを cargo +nightly build --release
みたいな感じでビルドすれば完成です. ./target/release/librust2python.so
が欲しかったパッケージです.
Python から呼び出す
作成したパッケージを Python から呼び出すためには, ./target/release/librust2python.so
をカレントディレクトリにコピーします. 同時に, Linux なら librust2python.so
を rust2python.so
へ, Windows なら librust2python.dll
を rust2python.pyd
へとリネームします. (コピーするより symlink を貼る方が楽なことが多い気もします.)
$ cp ./target.release/librust2python.so ./rust2python.so
これでめでたく Rust 製パッケージを呼び出せるようになりました.
$ python3
Python 3.7.2 (default, Jan 24 2019, 15:36:28)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import rust2python
>>>
>>> rust2python.hello()
Hello, world!
>>> rust2python.pi_times(4)
[0.0, 3.141592653589793, 6.283185307179586, 9.42477796076938]
関連項目
PyO3 に関するより詳細な情報が必要な場合, ユーザーガイド を読んでください. なお こちらの記事 は依存クレートなしで同じことを試みています.
追記 (2019-07-24) モジュールの作成に関する 続編 書きました.