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

Extend lifetime of holder references #3140

Closed
wants to merge 3 commits into from
Closed

Conversation

adamreichold
Copy link
Member

Closes #3138

@adamreichold
Copy link
Member Author

@davidhewitt I wanted to push this here to ask your opinion of whether using local variables to extend the holder lifetimes is a suitable approach or whether we should do something else here entirely?

handle_error(
extract_error_mode,
py,
quote! {
_pyo3::impl_::extract_argument::extract_argument(
#source,
&mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT },
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 could not produce a test case actually triggering the issue but changed it for completeness. I did put it into a separate commit though if it turns out that we do not need this after all.

@adamreichold adamreichold marked this pull request as ready for review May 6, 2023 11:34
@davidhewitt
Copy link
Member

Ah, well found!

So this is definitely a viable solution which has the nice property that it allows us to bind local variables inside the generated macro code.

I think the other option is to avoid binding locals at all and just have one giant tail expression, because then the holder references will just be temporaries which will get extended until the end of the expression (by which point the return value has been converted into Python types). Something like this patch seems to fix the bug to me:

diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs
index 01aedf695a..b7f967472d 100644
--- a/pyo3-macros-backend/src/method.rs
+++ b/pyo3-macros-backend/src/method.rs
@@ -425,9 +425,8 @@ impl<'a> FnSpec<'a> {

         let rust_call = |args: Vec<TokenStream>| {
             quote! {
-                let mut ret = function(#self_arg #(#args),*);
-                let owned = _pyo3::impl_::pymethods::OkWrap::wrap(ret, #py);
-                owned.map(|obj| _pyo3::conversion::IntoPyPointer::into_ptr(obj))
+                _pyo3::impl_::pymethods::OkWrap::wrap(function(#self_arg #(#args),*), #py)
+                    .map(|obj| _pyo3::conversion::IntoPyPointer::into_ptr(obj))
                     .map_err(::core::convert::Into::into)
             }
         };

(This same trick might need applying in a few of the other code paths which you've corrected too.)

Overall I'm happy with either approach. Being able to bind local variables makes the generated macro code easier for us to read and debug. On the other hand, the patch I put above keeps the implementation in pyo3-macros-backend simpler. So it's just a case of whatever we like best. I slightly prefer the conciseness of the patch I put above, though I am also fine with the PR as proposed here.

@adamreichold
Copy link
Member Author

adamreichold commented May 6, 2023

So it's just a case of whatever we like best. I slightly prefer the conciseness of the patch I put above, though I am also fine with the PR as proposed here.

I prepared #3142 with the tail-expression-based solution which is certainly a much smaller change and results less generated code which makes it the obvious choice for me.

The only concern I am wrangling with myself is that it does seem like a "trick" to me and I would have a hard time inferring this effect by looking at the code. At least in this regard, I find the solution using local variables more pedestrian and more importantly explicit in that it is easier to understand why this is done.

I am not completely convinced the effect is real though as I have never been on friendly terms with PyO3's proc macro code and this might just be me having a hard time doing anything with it.

@davidhewitt
Copy link
Member

Ok - let's go with #3142 for now.

I agree the macro implementations could use some refactoring, they've grown very... organically. 😅

What I'm hoping to move towards with that code is more structs which represent the parsed types, and then potentially more structs / functions to compose the generated code. E.g. this "ok-wrap and convert to PyErr" pattern can probably be its own function (or structure which implements quote::ToTokens, not sure what is more performant).

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.

E0716 "temporary value dropped while borrowed" in #[pyfunction]
2 participants