Skip to content
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

[RFC] split PyErr::new() into multiple methods #4413

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

birkenfeld
Copy link
Member

Intended to start a discussion, about APIs, but also names :)

Similar to call(), introduces new0(), new1() and new_args(), and the same on Exception types as new_err0(), new_err1() and new_err_args().

After the deprecation cycle, new1() could be renamed back to new(), since it is the one used in the vast majority of cases.

I kept PyErrArguments, but it's now only implemented for tuples, not for all Python-convertible types.

@davidhewitt
Copy link
Member

Related: I have been wondering for a while about a new call syntax and you have prompted me to actually write that up: #4414

Maybe we could have py_call!{ PyValueError(a, b, c) } ? 🤔

I think new1 here as suggested is "call this with one positional argument"? We should be careful as our .call1 for object calls is "call this object with a tuple of arguments".

@birkenfeld
Copy link
Member Author

Good point... I'm not fond of the names anyway, especially the new1 would probably be new in a green field, and could be after the deprecation cycle.

Could new0 be empty? IMO doesn't work too well for exceptions...

@birkenfeld
Copy link
Member Author

Changed the names to new_empty, new_arg, and new_args, with the expectation to rename new_arg back to new after two versions.

new_empty could also be done away with completely, since it's seldom used, and replaced by new_args(()).

Introduces new_empty(), new_arg() and new_args(), and
the same on Exception types as new_err_empty(), new_err_arg() and
new_err_args().

After the deprecation cycle, new_arg() could be renamed back to new(),
since it is the one used in the vast majority of cases.

I kept `PyErrArguments`, but it's now only implemented for tuples,
not for all Python-convertible types.
@birkenfeld birkenfeld mentioned this pull request Sep 13, 2024
3 tasks
Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks very much for pushing this forward, and I'm sorry I've been slow to engage deeply with the design.

I've added some comments with some reflections and ideas. I also opened #4584 after this PR prompted some additional thoughts.

PyErr::from_state(PyErrState::Lazy(Box::new(move |py| {
PyErrStateLazyFnOutput {
ptype: T::type_object(py).into(),
pvalue: PyTuple::new(py, &[arg.into_py(py)]).into(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems unfortunate that we have to (understandably) wrap everything inside a PyTuple to make things behave as intended here.

I wonder if there's performance justification to have a special case for e.g. strings (I assume one-arg strings are 99% of the calls).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, sure, we can do a check and only wrap if it's a tuple...

Comment on lines +47 to +54
/// Creates a new [`PyErr`] of this type with no arguments.
///
/// [`PyErr`]: https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html "PyErr in pyo3"
#[inline]
#[allow(dead_code)]
pub fn new_err_empty() -> $crate::PyErr {
$crate::PyErr::new_empty::<$name>()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It always felt weird to me that the function was e.g. PyValueError::new_err() -> PyErr and there is no obvious way to immediately create a ValueError.

I wonder, should we have e.g. PyValueError::new() -> Bound<'py, PyValueError> which can then be cast .into() PyErr? TBH, that might not be great either, having the .into() calls would feel quite clumsy.

Is there use for a macro here? e.g. return Err(py_err!(PyValueError(1))) or return py_err!(PyValueError(1, 2, 3))? That way we could automatically select the right constructor function according to the number of arguments.

All food for thought 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the into() directly give a Result<T, PyErr>? That might be ergonomic enough for many use cases.

As for the macro, I'm not sure it provides enough convenience to justify itself. Maybe yes if it reused the more general framework of a call macro?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants