Closed leoleoasd closed 2 years ago
@leoleoasd I think you can already achieve what you want by implementing From<ExternalType> for MyShapeError
, and returning MyShapeError
from your #[pyfunction]
implementations. If you also implement From<MyShapeError> for PyErr
, then PyO3 will accept that as the return type.
Something like this:
#[pyfunction]
fn function_returning_custom_error() -> Result<(), MyShapeError> { // Uses From<MyShapeError> for PyErr
array.push_row(ArrayView::from(&[1,2,3]))?; // Uses From<ExternalType> for MyShapeError
Ok(())
}
What if my function may return multiple types of errors?
You can still write a custom error enum
(e.g. maybe by thiserror
) with the same idea; as many custom error types as you like can convert to the custom enum and then you can implement a conversion from the enum to PyErr
.
Would this method still require a type conversion between custom error with custom enum?
EDIT: No.
I solved it! Just define a error enum like this:
// use quick_error package to generate some code
quick_error! {
#[derive(Debug)]
pub enum PreprocessErrorWrap {
Metis(err: metis::Error) {
// generate error source
source(err)
// and implement From<metis::Error> for PreprocessErrorWrap
from()
}
Shape(err: ndarray::ShapeError) {
source(err)
from()
}
IO(err: std::io::Error){
source(err)
from()
}
Python(err: pyo3::PyErr) {
source(err)
from()
}
}
}
And implement From for PyErr
:
impl std::convert::From<PreprocessErrorWrap> for PyErr {
fn from(err: PreprocessErrorWrap) -> PyErr {
match err {
PreprocessErrorWrap::Python(err) => {
err
}
_ => {
PreprocessError::new_err(err.to_string())
}
}
}
}
Then, a pyfunction returns Result<_, PreprocessErrorWrap>
would be accepted by PyO3 and any error in that enum would also be accepted.
Implementing
std::convert::From
forPyErr
is required a lot when calling rust functions from python. PyO3 does implement errors fromstd
usingimpl_to_pyerr
macro, but this macro isn't exported, so users need to make their own implementation for custom rust errors.Also, users can't implement
std::convert::From<ExternalType>
forPyErr
, they need to wrapExternalType
and add error conversion everywhere the error is encountered.For example: I created a macro to automate this wrapping process:
Basically this macro wraps the given error and implements
std::convert::From<$name> for PyErr
. And when I need to use it:I need this ugly
or_else
statement for EVERY SINGLE ERROR HANDLING.I don't know much about rust macros, for example, If PyO3 exports
impl_to_pyerr
and I call that macro from another package, can I break the "only traits defined in the current crate can be implemented for arbitrary types define and implement a trait or new type instead" rule? If this is possible, I can avoid theor_else
statement, and if not, I think lots user needs to handle errors other thanstd
's, so they needswrap_err!
macro.