[Rust] PyO3 で Python パッケージを作成

# はじめに
Rust, 速いし書きやすいし最高ですよね? でも普段データをプロットしたり解析したりするには Python 使ってますよね? Python から Rust の関数を呼び出せたらもっと最高ですよね?

[PyO3](https://crates.io/crates/pyo3) は Rust で Python パッケージを作成, または Python を Rust から呼び出す機能を持つクレートです. 使い方を知るには基本的には [ユーザーガイド](https://pyo3.rs/v0.7.0/) (英語) を読めば良いと思いますが, 現在の状況に基づく日本語の文献が乏しいので, 2019年7月時点の最新版 v0.7.0 に基づいて基礎的な部分を説明します. 

この記事では Rust で Python パッケージを作成する方法を扱います.

# 準備
Rust 1.34.0-nightly 以上, および Python 3.5 以上が実行可能な環境が既に整っていると仮定します. 2019年7月現在 __nightly 必須__ です ([この issue](https://github.com/PyO3/pyo3/issues/5)). なお本記事の内容は 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` に次の内容を追記します.

```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 に関することは [ガイド](https://pyo3.rs/v0.7.0/class.html) を, numpy を扱う方法については [rust-numpy](https://github.com/rust-numpy/rust-numpy) を見てください.

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

次に, Python から呼び出したい関数を `#[pyfunction]` アトリビュート付きで実装します. 戻り値は必ず `PyResult<T>` である必要があります. 戻り値のない関数を定義する場合は `PyResult<()>` とすればよいです. 例えば hello world なら

```rust

#[pyfunction]
fn hello() -> PyResult<()> {
    println!("Hello, world!");
    Ok(())
}
```

引数や戻り値がある場合もごく素直に書けばよいです. 例えば円周率の 0 倍, 1 倍, ..., n-1 倍のリストをつくる関数なら

```rust
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]` アトリビュート付きの関数として実現されます.

```rust
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 を貼る方が楽なことが多い気もします.)

```console
$ cp ./target.release/librust2python.so ./rust2python.so
```

これでめでたく Rust 製パッケージを呼び出せるようになりました.

```shell-session
$ 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 に関するより詳細な情報が必要な場合, [ユーザーガイド](https://pyo3.rs/v0.7.0/) を読んでください. なお [こちらの記事](https://qiita.com/0ncorhynchus/items/9bc48b4e074bfd6fc689) は依存クレートなしで同じことを試みています.

__追記 (2019-07-24)__ モジュールの作成に関する [続編](https://qiita.com/osanshouo/items/bd0fa84e16b13449d797) 書きました.