Rust で PyO3 を用いて Python 用ライブラリを作成していたのですが, ある関数の入力に関して制約があり (例えば平方根は入力が非負でなければいけない, のような), その制約を満たさない場合にエラー (ValueError
) を返す方法がわからず手間取りました. 半年くらい後にきっとまた同じことをググる羽目になるんじゃないかと思いますが, それは面倒なのでわかったことをまとめておきます.
※本記事は Rust 1.45.2, PyO3 0.11.1 を使用して2020年8月に動作確認しました.
本論
こんな感じになると思います1.
#[pyfunction]
fn sqrt_rs(value: f64) -> PyResult<f64> {
if value.is_sign_negative() {
return Err(pyo3::exceptions::ValueError::py_err(
"sqrtに負の値が入力されました. 非負の値を入力してください."
);
}
Ok(value.sqrt())
}
他にも pyo3::exeptions
に様々なエラー型が定義されています. 使い方はすべて同じで, py_err
関数を呼べば良いです.
解説
Python 側に露出する関数は Rust 側では PyResult<T>
型を返しますが, この型は何かというと PyResult<T> = Result<T, PyErr>
です. ですからエラーが発生したら PyErr
型のインスタンスを生成し, Err
で包んで return
すればよさそうです. それで, PyErr
型ってなんでしょう?
PyO3 では Python の様々なエラー型 (ValueError
や TypeError
など) をモジュール pyo3::exceptions
内で定義しています. これはすべて Rust 側でも異なる型なので, そのままでは扱いがやや面倒です. そこで PyO3 では様々なエラー型とその値を保持するラッパー構造体 PyErr
を用意しています. 各エラー型には PyErr
型を返す py_err
関数が付随していて, これに引数としてその値を渡すことで欲しいエラー型インスタンスを包んだ PyErr
インスタンスが取得できます.
参考文献
-
これは例示のためのコードなので,
NaN
は? とか,if else
を使えばreturn
不要, などの突っ込みを入れないでください. ↩