1
0

初めてのmaturin

Last updated at Posted at 2024-08-07

この記事は、2024/8/7に書かれた記事を2024/9/15に加筆改訂したものです。
元の記事は編集履歴からご覧ください。

RustをPythonから呼び出せるライブラリ、maturinの基本的な使用方法をまとめます。

事前準備

1. 必要なもののインストール

Python, Rust, maturinが必要です。
Pythonは公式ページからインストールしておいてください。
Rustとmaturinは以下のコマンドをそれぞれ入力すればインストールできます。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
pip3 install maturin

2. プロジェクト作成

maturinプロジェクトを新たに作成します。

maturin new -b pyo3 ./my_project

-bはmaturinが内部的に使うシステムを指定します。今回は最も人気なpyo3を使います。

これで準備完了です。
ディレクトリはこんな形になっているはずです。

my_module
├─ Cargo.toml
├─ pyproject.toml
└─ src
   └─ lib.rs

Rustコード

基本的な形は

sample.rs
#[pyなんとか]
(Rustコード)

です。
また、モジュールに含めるためには専用の関数定義を行う必要があります。

関数

Pythonから呼び出せる関数を定義します。

my_project/src/lib.rs
use pyo3::prelude::*; //忘れずに

#[pyfunction] //重要!
fn add_two(n: i64) -> i64 {
    n + 2
}

#[pyfunction]は、Pythonに関数を公開するために必要です。これがないとPython側から関数を参照できません。
また、Python側でエラーを起こしたい場合は、返り値をPyResult<()>と注釈した上でPyErrを返すこともできます。

クラス

Pythonのクラスを表現できます。

my_project/src/lib.rs
#[pyclass] // 重要!
struct MyClass {
    a: i64, 
    b: i64,
}

#[pymethods] // クラスのメソッドを定義
impl MyClass {
    // 普通のメソッド
    fn sum(&self) -> i64 {
        self.a + self.b 
    }

    // クラスメソッド
    #[classsmethod]
    fn class_method(cls: &Bound<'_, PyType>, a: i64, b: i64) -> i64 {
        // 第1引数を忘れるとエラー
        // クラスが要らない場合は#[staticmethod]が使える
        a + b
    }

    // 特殊メソッドも定義できる
    fn __str__(&self) -> String {
        format!("<MyClass with {} and {}", self.a, self.b)
    }
}

これらの他にも、コンストラクタなどを指定することができます

モジュール

my_project/src/lib.rs
#[pymodule] //重要!
fn my_module(m: &Bound<PyModule>) -> PyResult<()> {
    // 関数
    m.add_function(wrap_pyfunction!(add_two, m)?)?;
    // クラス
    m.add_class::<MyClass>()?; // やや変則的なので注意
    Ok(())
}

#[pymodule]は、RustクレートをPythonのモジュールとして公開するために必要です。これがないとImportErrorになります。

モジュールの作り方
maturinのガイドによると、

#[pymodule]
mod my_module {
    //関数
}

とすることでもPythonモジュールを作成できるようです。

#[pymodule] 関数
#[pymodule]が付いた関数は、そのプロジェクトの名前(Cargo.toml参照)と同じ名前でなければなりません。

my_moduleの引数
引数を&PyModuleとするとエラーになります。
@Yosh31207 様によると、これはPyO3(内部実装)の仕様変更によるもので、従来のAPIが変更されたことに起因するようです。

サブモジュール

サブモジュールは、専用の関数を使って作成すると親モジュールに登録できます。

rust: my_project/src/lib.rs
// 引数は前項の方法で受け取ったモジュールか、そのサブモジュールであるべきです
fn child_module(parent: &Bound<'_, PyModule>) -> PyResult<()> {
    // サブモジュールの作成
    let child_module = PyModule::new_bound(parent.py(), "child_module")?;

    // ここで親モジュールと同じように関数やクラスを登録できる
    
    parent.add_submodule(&child_module)
}

モジュールのビルド

コマンドラインで実際にモジュールを作成します。

maturin buildを使用する場合

以下のコマンドを実行するとビルドできます。その際、Cargo.tomlがあるディレクトリに移動することを忘れないでください

cd ./my_module
maturin build
cd ..

ただし、この状態ではPythonからのインポートはできません。別途pip installが必要です。

pip install ./target/wheels/*
maturin build --release

とすると最適化してくれます。

maturin developを使用する場合

仮想環境の構築が必要ですが、pip installを省けます。

cd ./my_module
maturin develop
cd ..

これでモジュールがインストールできたはずです。

実際に使う

ディレクトリ構成

.
├─main.py
└─my_module

pyファイルの位置
my_moduleディレクトリ内に.pyファイルを含めないでください。AttributeErrorが送出されます。1

main.pyの例:

main.py
import my_module # pip installしたモジュールをインポートするときと同じ

assert my_module.add_two(6) == 8

その他

  • 実はPythonプログラムとRustプログラムを同じディレクトリに入れることも可能です。その場合はこんなディレクトリ構成になります

  • #[pyo3(name = "..")]でモジュールの公開名を変えられます

  • #[args()]で引数のデフォルト値を指定できます

  • maturin developコマンドを使うと、変更をリアルタイムで反映できます

参考

実験した環境

Github Actions(コマンドラインがわり)

  • ubunstu-latest(ubuntsu 22.0.4)
  • CPython 3.12.4 (Python 3.12)
  • rustc 1.80.0
  • maturin 1.7.0
  1. この時、maturinはmy_moduleをpure-Rustではなく、PythonとRustの混合プロジェクトと解釈します。今回のディレクトリ構成は混合プロジェクトには適していないので、モジュールは何も含まない空のモジュールになってしまいます。正しいディレクトリ構成はその他章を参照してください。

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0