Python3からRust関数を呼び出す

### 2020/12/02 追記
Rustでpythonパッケージを作るためのクレートである[PyO3](https://crates.io/crates/pyo3)がv0.11.0からRustのstable版でコンパイルできるようになっています。
最近私がPythonを触る機会が少ないのでPyO3を試せていないのですが、この記事で書かれている方法よりはPyO3を使ったほうが幸せになれると思うのでここに追記しておきます。

# TL;DR

PythonからCやC++などの低水準のプログラミング言語の関数を呼びたい場合は、CythonやBoost.Pythonを使うのが標準だと思いますが、Rustにはこれが定石というものがありません。
一応[cpython](https://github.com/dgrunwald/rust-cpython)というRustライブラリがあるらしいですが、今回は使いません（使い方が良く分からなかったという理由だったり）。

ここではRustの[公式ドキュメント](https://doc.rust-lang.org/book/)を参考に、PythonからRustの関数を呼び出す方法を模索しようと思います。

当然ながらRustプロジェクトのビルドには`cargo`を使用します。


# 引数、返り値のない例

まず、Pythonから呼ばれるRustの関数に引数と返り値のない簡単な例を実装します。ついでに[ここ](https://doc.rust-lang.org/1.2.0/book/rust-inside-other-languages.html)を参考にしてます。

まずは`cargo new`で新しいプロジェクトを作ってください。

Rust側のコードはこんな感じにします。

```rust:lib.rs
#[no_mangle]
pub extern fn rust_fn() {
    println!("This is a rust function.");
}
```

`#[no_mangle]`は関数名のマングリングを行わないようにしてくれます。
また、Rustの外部に関数を渡せるように`extern`修飾子も忘れないでください。

次に`Cargo.toml`には以下を追記します。

```toml:Cargo.toml
[lib]
name = "embed"
crate-type = ["dylib"]
```

ライブラリの名前は適当にしてください。ここでは`embed`にしています。
共有ライブラリを作りたいので`crate-type`は`dylib`を指定します。

そして、こいつをビルドします。

```bash
$ cargo build --release
```

これで、`target/release/libembed.so`が生成されますので、これを使うPythonスクリプトを用意しましょう。

```py3:main.py
from ctypes import cdll

lib = cdll.LoadLibrary("target/release/libembed.so")

lib.rust_fn()

print("done!")
```

`ctypes`はPythonからC互換のライブラリを扱うためのパッケージで、`cdll`関数を使ってライブラリを読み込みます。
これを実行すると以下のように出力されます。

```shell-session
$ python3 main.py
This is a rust function.
done!
```

無事`rust_fn`を呼び出せました。


# 引数ありの例

次は引数がある場合を実装してみます。

```rust:lib.rs
#[no_mangle]
pub extern fn rust_fn(x: i32) {
    println!("called with {}.", x);
}
```

```py3:main.py
from ctypes import cdll

lib = cdll.LoadLibrary("target/release/libembed.so")

lib.rust_fn(100)

print("done!")
```

実行結果はこうなります。

```shell-session
$ cargo build --release
$ python3 main.py
called with 100.
done!
```

これもうまく動いてます。

今度は浮動小数点数を引数に取ってみましょう。

```rust:lib.rs
#[no_mangle]
pub extern fn rust_fn(x: f32) {
    println!("called with {}", x);
}
```

```py3:main.py
from ctypes import cdll

lib = cdll.LoadLibrary("target/release/libembed.so")

lib.rust_fn(2.718)

print("done!")
```

実行

```shell-session
$ cargo build --release
$ python3 main.py
Traceback (most recent call last):
  File "main.py", line 5, in <module>
    lib.rust_fn(2.718)
ctypes.ArgumentError: argument 1: <class 'TypeError'>: Don't know how to convert parameter 1
```

はいダメでしたー。

`float`型である`2.718`を変換する方法がわからないと怒られてます。

Python内での`rust_fn`の引数の型を見てみましょう。
`rust_fn`の`argtypes`属性で確認できます。Pythonインタプリタを起動して見てみましょう。

```pycon
Python 3.6.0 (default, Jan 16 2017, 12:12:55)
[GCC 6.3.1 20170109] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import cdll
>>> lib = cdll.LoadLibrary("target/release/libembed.so")
>>> print(lib.rust_fn.argtypes)
None
```

`None`になってますね。

`cdll`でロードされた関数群は引数なしで返り値は`int`として処理されるらしいです。
こういう時は、自分で引数と返り値の型を指定しなくてはいけません。
一つ制約として、`argtypes`に代入するのは`ctypes`の型のリストのみとなっているので注意が必要です。
`ctypes`の型はC互換の型を意味しています。ここではRustの`f32`に対応する型が欲しいので仮に`c_float`を使います。

```py3:main.py
from ctypes import cdll, c_float

lib = cdll.LoadLibrary("target/release/libembed.so")

lib.rust_fn.argtypes = (c_float,)

lib.rust_fn(2.718)

print("done!")
```

```shell-session
$ python3 main.py
called with 2.718.
done!
```

できましたね。

対応する型の対応表は[こちら](https://docs.python.jp/3/library/ctypes.html#fundamental-data-types)に書いてありますので参考にしてください。

ここの`c_float`を間違って`c_double`とかにすると実行はできますがおかしな結果になってしまうので注意です。

ここで、Rustの`f32`が`c_float`にしましたが、この対応はアーキテクチャ依存かもしれません（ABI周りの知識がないのでよく分からない）。ですのでRust側でも明示的に`c_float`に対応していると指定したいですね。

今度は`lib.rs`を変更します。Rustで`c_float`等のC互換の型を使うには`libc`を使います。

```rust:lib.rs
extern crate libc;
use libc::c_float;

#[no_mangle]
pub extern fn rust_fn(x: c_float) {
    println!("called with {}.", x);
}
```

`Cargo.toml`に以下を追記してください。

```toml:Cargo.toml
[dependencies]
libc = "0.2.21"
```

これでちゃんと`c_float`を明示的に引数の型として指定できました。
これもコンパイルして実行すると同様な結果が出てきます。


# 返り値もある場合

前述したように`cdll`でロードした関数はデフォルト返り値が`int`だと認識されています。ですので引数同様こちらで正しい型をPythonに教えてあげなくてはいけません。
とりあえずRust側の関数を定義しましょう。

```rust:lib.rs
extern crate libc;
use libc::c_float;

#[no_mangle]
pub extern fn twice(x: c_float) -> c_float {
    x * 2.0
}
```

引数の値を２倍にして返す`twice`という関数を定義しました。ここでも浮動小数点数は`c_float`としてます。
この関数をPython側から呼びましょう。

```py3:main.py
from ctypes import cdll, c_float

lib = cdll.LoadLibrary("target/release/libembed.so")

lib.twice.argtypes = (c_float,)
lib.twice.restype = c_float

x = 2.718
ans = lib.twice(x)
print("twice({}) = {}".format(x, ans))

print("done!")
```

ロードされた関数の返り値は`restype`で参照及び更新できます。
これを実行すると以下のようになります。

```shell-session
$ python3 main.py
twice(2.718) = 5.435999870300293
done!
```

結果が`2.718`の２倍の`5.436`にぴったりではないのは単精度計算ゆえの誤差ですので、うまく動いています。
これでPythonからRustの関数を引数、返り値ありで呼ぶことができました。


# さいごに

引数や返り値に配列や構造体を使いたい場合はもう少しややこしいのでここでは扱いません。
ですが、関数周りの扱いはここでやった事とほぼ同じです。
CやC++でPythonの中身の実装するのが苦行になってきたという方はどしどしRustで実装していきましょう！
それでは。

# 参考文献

* https://doc.rust-lang.org/1.2.0/book/rust-inside-other-languages.html
* https://doc.rust-lang.org/1.2.0/book/ffi.html
* https://docs.python.jp/3/library/ctypes.html