-
Notifications
You must be signed in to change notification settings - Fork 758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proto #1: Exception instances as Py<BaseException> #686
Conversation
This prototype implements use of Py<BaseException> as the instance to use for exception instances. These instances integrate reasonably well with the Rust’s standard error mechanisms by implementing the `Display` and `Error` traits. These error types can also be stored into e.g. `failure::Fail`s or other error types as a cause of some greater error.
cc @kngwyu |
impl std::error::Error for BaseException { | ||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | ||
unsafe { | ||
// Returns either `None` or an instance of an exception. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to obtain the GIL.
unsafe impl PyTypeObject for $name { | ||
fn init_type() -> std::ptr::NonNull<$crate::ffi::PyTypeObject> { | ||
unsafe { std::ptr::NonNull::new_unchecked(ffi::$exc_name as *mut _) } | ||
} | ||
} | ||
|
||
impl<'v> PyTryFrom<'v> for $name { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upcasting to PyAny
is indeed a missing part 👍
impl std::fmt::Debug for BaseException { | ||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
// Sneaky: we don’t really need a GIL lock here as nothing should be able to just mutate | ||
// the "type" of an object, right? RIGHT??? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I also am not sure 😓
We need tests.
let py_type_name = unsafe { CStr::from_ptr((*(*self.as_ptr()).ob_type).tp_name) }; | ||
let type_name = py_type_name.to_string_lossy(); | ||
f.debug_struct(&*type_name) | ||
// TODO: print out actual fields! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can access actual field only by type name?
Or we need to get the global PyExc_~
s here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By "actual fields" I meant the fields of the PyException_HEAD
structure component, which is defined as such:
#define PyException_HEAD PyObject_HEAD PyObject *dict;\
PyObject *args; PyObject *traceback;\
PyObject *context; PyObject *cause;\
char suppress_context;
At very least values of args
, traceback
, context
, cause
and suppress_context
are interesting, but I can imagine it being useful to also print out at least the refcount
from PyObject_HEAD
too.
f.debug_struct(&*type_name)
is just a builder for debug output. See https://doc.rust-lang.org/stable/std/fmt/struct.Formatter.html#method.debug_struct. The final result should look something like
f.debug_struct(type_name).field("args", &self.args).field("traceback", &self.traceback)/* etc... */.finish()
// must return a `&Py<BaseException>` instead… but we cannot do that because | ||
// nothing is storing such a thing anywhere and thus we cannot take a reference to | ||
// that… |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I feel it difficult to understand why we cannot use Py<Exception>
here... 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot return a Py<Exception>
because the trait contract requires:
fn source(&self) -> Option< & (dyn std::error::Error + 'static)>
The most important detail is that this method is returning a reference with a lifetime tied to &self
(I tried to make it more visible by adding whitespace around it). In other words, it requires that you return a reference to something in Self
. Trying to return a Py<BaseException>
would result in the typical "value does not live long enough" error, very much like the snippet below would:
fn bogus(&self) -> &str {
&String::from("banana") // cannot compile because `String` does not live long enough...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks.
Thank you, this would be a big step for us. |
let gil = Python::acquire_gil(); | ||
let py = gil.python(); | ||
if let Ok(s) = crate::ObjectProtocol::str(&*py_self.as_ref(py)) { | ||
write!(f, ": {}", &s.to_string_lossy()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FIXME: if the resulting string is empty we want to not add the :
.
@nagisa I'm curious if you plan to continue with this PR? If not, I'd be happy to take it on, rebase it on master and address the changes reviewed here. |
@davidhewitt you’re welcome to take over this. I still think it is very worthwhile to do this but I also no longer have much drive to work on this feature as I implemented something simpler, although less elegant, directly in my code. |
I noticed we lack the link to the most relevant issue #682 |
I have a PR which is almost ready to open as a draft to replace this one. I've stuck to the original idea here but gone a bit further:
|
Superseded by #1024 |
This prototype implements use of
Py<BaseException>
as the instance touse for exception instances. These instances integrate reasonably well
with the Rust’s standard error mechanisms by implementing the
Display
and
Error
traits. These error types can also be stored into e.g.failure::Fail
s or other error types as a cause of some greater error.There are a couple of sketchy points in this PR most notable of which is
the implementation of
std::error::Error::source
. See the included FIXME.This is also somewhat of an experiment around the ownership-based
Py<>
that I have outlined in #679 and the issues
implementing
source
appear to stem from exactly the fact that Python’s GILand Rust’s ownership are fairly incompatible.
FWIW: I think this might be revealing a soundness issue in our current setup too.