1

I have re-written some Python into Rust using pyO3. I have a situation where my Rust code can panic somewhere in a third party Rust library I use. The simplest thing for me would be to catch that panic in Python-land and fall back to the slower (but more resilient) Python method. The failure is rare enough that falling back to Python is still more efficient than only-Python. Is there any way to deal with the Rust panic? From what I've read the answer is probably "no," but I'm hoping!

Below is simplified example of what I mean. Any help is appreciated!

(I know I could/can track down the specific reason for each panic, but I think this question has merit anyway.)

lib.rs

use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
use pyo3::{pymodule, types::PyModule, PyResult, Python};

#[pymodule]
fn can_panic(_py: Python, m: &PyModule) -> PyResult<()> {
    #[pyfn(m)]
    fn can_panic<'py>(
        py: Python<'py>,
        arr: PyReadonlyArray1<i64>,
    ) -> &'py PyArray1<bool> {
        let my_arr = arr.as_array();
        for v in my_arr {
            if v == &-1 {
                panic!("Hey, you can't do that!");
            }
        }
        vec![true; my_arr.len()].into_pyarray(py)
    }

    Ok(())
}

Cargo.toml

[package]
name = "can_panic"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "can_panic"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.18.3", features = ["extension-module", "anyhow"] }
numpy = "0.18.0"
nalgebra = "0.32.2"

testing.py

import numpy as np
import can_panic

print("Staring filtering")

print(can_panic.can_panic(np.array([1, 2, 3], dtype="int64")))

print("Done filtering")

print("Staring Filtering")

try:
    print(can_panic.can_panic(np.array([-1, 2, 3], dtype="int64")))
except Exception:
    print("didn't work")
    # call Python equivalent

print("Done filtering")

output

Staring filtering
[ True  True  True]
Done filtering
Staring Filtering
thread '<unnamed>' panicked at 'Hey, you can't do that!', src/lib.rs:16:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Traceback (most recent call last):
  File "(snip...)/can_panic/testing.py", line 13, in <module>
    print(can_panic.can_panic(np.array([-1, 2, 3], dtype="int64")))
pyo3_runtime.PanicException: Hey, you can't do that!
Jmb
  • 18,893
  • 2
  • 28
  • 55
Stephen
  • 107
  • 9

1 Answers1

1

Your except doesn't work because as pyo3 documents PanicException derives from BaseException (like SystemExit or KeyboardError) as it's not necessarily safe (given not all rust code is panic safe, pyo3 does not assume a rust-level panic is innocuous).

Your options then would be, in decreasing order of desirability (as far as I'm concerned):

  • improve the third-party library or your call to it so it does not panic
  • use catch_unwind on the Rust side, and convert the panic to a more classic Error
  • catch the PanicException specifically, which is not great (and possibly not easy) as you probably dont' want to catch BaseException
Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • 1
    Thanks. A few more details – I'm doing numerical fitting, which is a dark art, and sometimes it goes to dark places. In that case, I'd like it to tell me "I didn't fit" rather than "screw you," so I went with `catch_unwind`. – Stephen Jun 01 '23 at 18:53