PythonからRustを呼んで高速化！ PyO3 チュートリアル：簡単な関数をラップする　その➁

## 概要

1.　Pyhtonでアルゴリズムまで書いてあるのは速度面では好ましくないな〜
2.　よし、C, C++あたりで書いてあるものを探して、それをPythonから呼んで高速化しよう。
3.　なかなかいいライブラリ見つからんな、
4.　おっ、Rustていう言語で書かれてるのならあったぞ
5.　RustてPythonから呼べんのか？？

これは、[PythonからRustを呼んで高速化！ PyO3 チュートリアル：簡単な関数をラップする　その➀](https://qiita.com/kenmaro/items/5919052a9d1f816b6fc4)

の続きになります。


## 今回の目標
今回は、
- `lib.rs`を追記
- `setup.py` を加え実際にコンパイル

していきます。

最終的な目標は、
```Rustで書いた関数やクラス（的なもの？）をPythonから気軽に呼べるようになること```
です。


## Rustの関数をPythonへ引き渡す

```rust

//lib.rsの続き
use pyo3::{wrap_pyfunction};

// ======================CREATING MODULE FOR PYTHON==================================================
/// This module is a python module implemented in Rust.
#[pymodule]
fn test_library(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(get_prime_numbers))?;

    Ok(())
}

```

ここでは、
```#[pymodule]```のデコレータが```test_library```がPythonのモジュールになることを宣言しています。

そして、先ほどの```get_prime_numbers```関数が、

```rust

m.add_wrapped(wrap_pyfunction!(get_prime_numbers))?;
```
でそのモジュールに関数としてラップされています。

最後に　`Ok(())`となっていて、このモジュールが関数として定義され、
```rust
PyResult<()> 
```
空の`PyResult`を返すようになっている（Void関数はこれでラップできる）のが少し気になりますが、
この辺はおまじないとしてチュートリアルをそのまま書きました。


## setup.py を作る
Cythonでも使用したおなじみの`setup.py`を作っていきます。
今回はシンプルで簡単です。

```setup.py
from setuptools import setup
from setuptools_rust import Binding, RustExtension

setup(name='test_library',
        version='0.1',
        rust_extensions=[
            RustExtension('test_library', 'Cargo.toml',
                binding=Binding.PyO3)],
            zip_safe=False)
```

```python
name='test_library',
```
によりPython側から`import test_library`のように呼べます。

```python
RustExtension('test_library', 'Cargo.toml',
                binding=Binding.PyO3)],
```
で`lib.rs`の中の`test_library` モジュールを`pyo3`によってライブラリ化します。
この時、使う依存関係が`Cargo.toml`と書くだけなのがとても簡単ですね。。

## ビルドしてみる
以上で準備ができたので、実際にビルドしPythonから関数を呼んでみます。

```bash

python setup.py install
```

を実行し、テストコード

```test.py
import test_library 
import time
import sys

def get_prime_numbers(n: int):
    flags = [True for _ in range(n+2)]

    upper = int(n ** 0.5)
    for i in range(2, upper+1):
        if not flags[i]:
            continue

        prime = i

        j = prime * 2
        while j <= n:
            flags[j] = False
            j += prime

    primes = []
    for i in range(2, n+1):
        if flags[i]:
            primes.append(i)

    return primes

if __name__  == "__main__":
    # just calling rust function from created library
    a = 123
    b = 456
    c = test_library.sum_as_string(a, b)

    print(c)


    
    # just calling rust function from created library, to find primes
    # rust is (of course) a lot faster than python
    # if you wanna call python function, python test.py, 
    # if you wanna call rust   function, python test.py --rust
    
    use_rust = len(sys.argv) == 2 and sys.argv[1] == "--rust"
    n = 10000


    t1 = time.time()
    for _ in range(10):
        if use_rust:
            primes = test_library.get_prime_numbers(n)
        else:
            primes = get_prime_numbers(n)

    t2 = time.time()

    print(f"time took is: {t2-t1} sec")

```
> [RustモジュールをPythonから実行する (PyO3)](https://ohke.hateblo.jp/entry/2020/02/15/230000)から引用

を実行します。

```
python test.py
``` 
でpythonベースの関数が走り、

```
python test.py --rust
```
でRustベースの関数を呼んでいます。


結果、

```bash

$ python test.py
time took is: 0.013466835021972656 sec
``` 

```bash

$ python test.py --rust
time took is: 0.0005574226379394531 sec
``` 

となり、実行速度差が顕著に現れてくれました。


## まとめ

最終的な目標は、
```Rustで書いた関数やクラス（的なもの？）をPythonから気軽に呼べるようになること```
でしたが、
今回でRustで書かれた関数をPythonから呼ぶ方法を解説しました。
まだカバーした型変換が`Vec -> List` だけなので、次の記事でまた他の型に関しても書ければと思います。ただ、あまり記述は変わらないので、簡単です。

また、Rustのクラス的なもの(Struct + method)をPython側のクラスオブジェクトとしてパースするやり方に関しても書いていきたいと思います。


今回はこの辺で。


おわり。