I'm trying to write a Python wrapper to some Rust code, which does some TryFrom
conversions internally:
pub enum MicrophoneType {
Bidirectional,
Omnidirectional,
}
impl TryFrom<char> for MicrophoneType {
type Error = &'static str;
fn try_from(x: char) -> Result<Self, Self::Error> {
match x {
'b' => Ok(Self::Bidirectional),
'o' => Ok(Self::Omnidirectional),
_ => Err("MicrophoneType: Invalid character given"),
}
}
}
#[pyfunction]
fn compute(
_py: Python,
microphone: char,
) -> PyResult<()> {
let _microphone = MicrophoneType::try_from(microphone)?;
Ok(())
}
However I'm seeing the error message
error[E0277]: `?` couldn't convert the error to `PyErr`
--> rir_generator_py/src/lib.rs:59:59
|
58 | ) -> PyResult<()> {
| ------------ expected `PyErr` because of this
59 | let _microphone = MicrophoneType::try_from(microphone)?;
| ^ the trait `From<&str>` is not implemented for `PyErr`
and I am confused as to why.
It seems that PyO3 does not know what to do with the type type Error = &'static str;
in my impl
, right? But isn't that the default way of giving error messages? Shouldn't PyO3 expect these, if it's a common pattern? What am I missing here?
After finding these docs I've realized a few things things:
Firstly, it's not a good pattern to use type Error = &'static str;
, instead you should use a custom error that can be match
ed and From
/Into
ed, if needed
#[derive(Debug)]
pub struct InvalidMicrophoneCharError {}
impl Error for InvalidMicrophoneCharError {}
impl fmt::Display for InvalidMicrophoneCharError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Incorrect microphone character")
}
}
impl TryFrom<char> for MicrophoneType {
type Error = InvalidMicrophoneCharError;
fn try_from(x: char) -> Result<Self, Self::Error> {
match x {
'b' => Ok(Self::Bidirectional),
'o' => Ok(Self::Omnidirectional),
_ => Err(InvalidMicrophoneCharError {}),
}
}
}
Secondly, you have to use the newtype pattern to create a "From
-chain" that starts with your type and ends with a PyErr
, which can then be combined with PyO3's support for Rust-native Result<T, E>
s:
struct MyInvalidMicrophoneCharError(InvalidMicrophoneCharError);
impl From<MyInvalidMicrophoneCharError> for PyErr {
fn from(err: MyInvalidMicrophoneCharError) -> Self {
PyValueError::new_err(err.0.to_string())
}
}
impl From<InvalidMicrophoneCharError> for MyInvalidMicrophoneCharError {
fn from(other: InvalidMicrophoneCharError) -> Self {
Self(other)
}
}
#[pyfunction]
fn compute(
_py: Python,
microphone: char,
) -> Result<(), MyInvalidMicrophoneCharError> {
let _microphone = MicrophoneType::try_from(microphone)?;
Ok(())
}
or skip the chain and simply use map_err()
#[pyfunction]
fn compute(
_py: Python,
microphone: char,
) -> Result<(), MyInvalidMicrophoneCharError> {
let _microphone = MicrophoneType::try_from(microphone)
.map_err(|error| PyValueError::new_err(error.to_string()))?;
Ok(())
}