diff --git a/guide/src/exception.md b/guide/src/exception.md index bfa1d746..4f493a29 100644 --- a/guide/src/exception.md +++ b/guide/src/exception.md @@ -92,18 +92,16 @@ This is an alias for the type `Result`. A [`PyErr`](https://pyo3.github.io/PyO3/pyo3/struct.PyErr.html) represents a Python exception. Errors within the `PyO3` library are also exposed as Python exceptions. -To handle rust errors, `std::convert::From` and [`ToPyErr`](https://pyo3.github.io/PyO3/pyo3/trait.ToPyErr.html) -traits need to be implemented for `PyErr`. The [`ToPyErr`](https://pyo3.github.io/PyO3/pyo3/trait.ToPyErr.html) trait provides -a way to convert Rust errors arguments to Python exception arguments. +PyO3 library handles python exception in two stages. During first stage `PyErr` instance get +created. At this stage python gil is not required. During second stage, actual python +exception instance get crated and set to python interpreter. -```rust -pub trait ToPyErr { - fn arguments(&self, _: Python) -> PyObject; -} -``` - -It's implemented for most of the standard library's error types so that you use [`Result::map_err()`](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err) to -transform errors to Python exceptions as well as taking advantage of `try!` macro or `?` operator. +In simple case, for custom errors support implementation of `std::convert::From` trait +for this custom error is enough. `PyErr::new` accepts arguments in form +of `ToPyObject + 'static`. In case if `'static` constraint can not be satisfied or +more complex arguments are required [`PyErrArgument`](https://pyo3.github.io/PyO3/pyo3/trait.PyErrArguments.html) +trait can be implemented. In that case actual exception arguments creation get delayed +until `Python` object is available. ```rust use std; @@ -112,13 +110,7 @@ use pyo3::{PyErr, PyResult, ToPyErr, exc}; impl std::convert::From for PyErr { fn from(err: std::io::Error) -> PyErr { - PyErr::from_value::(PyErrValue::ToErr(Box::new(err))) - } -} - -impl ToPyErr for std::io::Error { - fn arguments(&self, py: Python) -> PyObject { - (self.raw_os_error().unwrap_or(0), self.description()).to_object(py) + exc::OSError.into() } } @@ -130,3 +122,16 @@ fn connect(py: Python, s: String) -> PyResult { ``` The code snippet above will raise `OSError` in Python if `TcpListener::bind()` return an error. + +`std::convert::From` trait is implemented for most of the standard library's error +types so `try!` macro or `?` operator can be used. + +```rust +use pyo3::{PyResult, ToPyErr}; + +fn parse_int(py: Python, s: String) -> PyResult { + Ok(s.parse::()?) +} +``` + +The code snippet above will raise `ValueError` in Python if `String::parse()` return an error. diff --git a/src/err.rs b/src/err.rs index 93db23be..b0aaf8a8 100644 --- a/src/err.rs +++ b/src/err.rs @@ -103,7 +103,7 @@ macro_rules! py_exception { pub enum PyErrValue { None, Value(PyObject), - ToErr(Box), + ToErr(Box), ToObject(Box), ToTuple(Box), } @@ -112,33 +112,31 @@ pub enum PyErrValue { pub struct PyErr { /// The type of the exception. This should be either a `PyClass` or a `PyType`. pub ptype: Py, + /// The value of the exception. /// - /// This can be either an instance of `ptype`, + /// This can be either an instance of `PyObject`, /// a tuple of arguments to be passed to `ptype`'s constructor, /// or a single argument to be passed to `ptype`'s constructor. /// Call `PyErr::instance()` to get the exception instance in all cases. pub pvalue: PyErrValue, + /// The `PyTraceBack` object associated with the error. pub ptraceback: Option, } - /// Represents the result of a Python call. pub type PyResult = Result; - /// Marker type that indicates an error while downcasting pub struct PyDowncastError; - -/// Conversion trait that allows various errors to be converted into `PyErr` -pub trait ToPyErr { +/// Helper conversion trait that allows to use custom arguments for exception constructor. +pub trait PyErrArguments { /// Arguments for exception fn arguments(&self, Python) -> PyObject; } - impl PyErr { /// Creates a new PyErr of type `T`. /// @@ -425,7 +423,7 @@ impl std::fmt::Debug for PyErr { /// Converts `PyDowncastError` to Python `TypeError`. impl std::convert::From for PyErr { fn from(_err: PyDowncastError) -> PyErr { - PyErr::new::(()) + exc::TypeError.into() } } @@ -445,17 +443,15 @@ impl std::convert::From for std::io::Error { macro_rules! impl_to_pyerr { ($err: ty, $pyexc: ty) => { - impl $crate::ToPyErr for $err { - fn arguments(&self, py: $crate::Python) -> PyObject { + impl PyErrArguments for $err { + fn arguments(&self, py: Python) -> PyObject { self.description().to_object(py) } } - impl $crate::std::convert::From<$err> for $crate::PyErr { - fn from(err: $err) -> $crate::PyErr { - $crate::PyErr::from_value::<$pyexc>( - $crate::err::PyErrValue::ToErr(Box::new(err)) - ) + impl std::convert::From<$err> for PyErr { + fn from(err: $err) -> PyErr { + PyErr::from_value::<$pyexc>(PyErrValue::ToErr(Box::new(err))) } } } @@ -498,7 +494,7 @@ impl std::convert::From for PyErr { /// Extract `errno` and `errdesc` from from `io::Error` -impl ToPyErr for io::Error { +impl PyErrArguments for io::Error { fn arguments(&self, py: Python) -> PyObject { (self.raw_os_error().unwrap_or(0), self.description()).to_object(py) } @@ -511,7 +507,7 @@ impl std::convert::From ToPyErr for std::io::IntoInnerError { +impl PyErrArguments for std::io::IntoInnerError { fn arguments(&self, py: Python) -> PyObject { self.description().to_object(py) } diff --git a/src/lib.rs b/src/lib.rs index 5f477a52..9c835f4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,7 +150,7 @@ pub mod ffi { pub use ffi3::*; } -pub use err::{PyErr, PyErrValue, PyResult, PyDowncastError, ToPyErr}; +pub use err::{PyErr, PyErrValue, PyResult, PyDowncastError, PyErrArguments}; pub use objects::*; pub use objectprotocol::ObjectProtocol; pub use object::PyObject;