はじめに

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 が自動的に遂行してくれますが, ある程度対応関係を把握しておかないとコードを書けません. そこで最初に型の対応関係についてまとめておきます.

RustPythoni32, usizeintf32, f64floatboolboolVec<T>listStringstrHashMapdict

つまりは普通に使う型は極めて普通に対応しているため, たいていうまくいきます. ちなみに関数の引数に入れようとして型エラーが発生した場合も普通に Python の方でエラーが発生するだけです.

なお struct/class に関することは ガイド を, numpy を扱う方法については rust-numpy を見てください.

パッケージ作成

それでは src/lib.rs の中身を作成しましょう. まずは use pyo3::prelude::*; により必要なものをインポートしておきます.

次に, Python から呼び出したい関数を #[pyfunction] アトリビュート付きで実装します. 戻り値は必ず PyResult である必要があります. 戻り値のない関数を定義する場合は 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) モジュールの作成に関する 続編 書きました.

bannerAds