-
Notifications
You must be signed in to change notification settings - Fork 778
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
Added as_super
methods to PyRef
and PyRefMut
.
#4219
Conversation
…ocstrings and tests. The implementation of these methods also required adding `#[repr(transparent)]` to the `PyRef` and `PyRefMut` structs.
… use the new `as_super` methods. Added the `PyRefMut::downgrade` associated function for converting `&PyRefMut` to `&PyRef`. Updated tests and docstrings to better demonstrate the new functionality.
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.
Thanks, this makes a lot of sense! Just a brief thought on the implementation details and one typo...
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.
WIth the new ptr_from_ref
, let's please clean up the implementation to the newer style and then 👍 let's merge!
…rade` to use `.cast()` instead of `as _` pointer casts. Fixed typo.
…ref` for the initial cast to `*const _` instead of `as _` casts.
Thanks for the review David! I made the changes as requested. I definitely like your approach of
A couple other thoughts:
|
Actually it seems like |
Great questions!
I went back and forth over this, I see upsides to both. Given the runtime cost should be the same I'm happy to keep what you already have here.
Sure thing, we can expose it. Maybe as a follow up PR?
It would be great to update the documentation in this PR, yes please.
I'd skipped |
…tr_from_ref` added in PR PyO3#4240. Updated `PyRefMut::as_super` to use this method instead of `as *mut _`.
… class instead of `as_ref`, and updated the subsequent example/doctest to demonstrate this functionality.
I'll work on fixing the checks, but in the meantime:
I updated the docs, as well as the example code that follows it. Let me know if you have any concerns about the wording.
I added this as well. A couple notes however:
|
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.
Looks perfect, thanks!
I'll follow up sometime to remove constness from ptr_from_ref
; thanks for flagging that. I don't think lack of constness will change the runtime cost.
@@ -0,0 +1,3 @@ | |||
- Added `as_super` methods to `PyRef` and `PyRefMut` for accesing the base class by reference | |||
- Updated user guide to recommend `as_super` for referencing the base class instead of `as_ref` |
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.
No need to include docs changes or internal changes here, but as this PR is otherwise great let's just leave these points here and I'll tidy up during release.
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.
Good to know, I'll keep that in mind next time. Thanks for the merge!
Just following up here, it looks like So no changes needed in PyO3! 😄 |
Well I guess I'm losing it.... I swear I saw it not being const 😅 maybe I was looking at an earlier draft of it or something. But that's good to hear, I was very surprised when I thought it wasn't. |
Introduction
This PR simplifies the process of accessing superclasses by adding an
as_super
method toPyRef
andPyRefMut
that lets you convert&PyRef<T>
to&PyRef<T::BaseType
and&mut PyRefMut<T>
to&mut PyRefMut<T::BaseType>
. This acts as a hybrid between the existingas_ref
andinto_super
methods, providing both the by-reference ergonomics ofas_ref
with the super-superclass access ofinto_super
while unifying the concepts under a more intuitive/idiomatic pattern.What's wrong with
<PyRef<T> as AsRef<T::BaseType>>::as_ref
?As mentioned in the docs, calling
as_ref
can give you&T::BaseType
but not the super-superclass and beyond. But on top of that,AsRef::as_ref
is a general-purpose method used throughout the Rust language, and it is not intuitive that this is how you do the pyo3-specific task of referencing the base class. Sure, the existence of anAsRef<T::BaseType>
impl is useful if you want a define a function that is generic over any type that can be used to get&T::BaseType
, but this should not be the primary method for doing so (just likeInto::into
should not be the primary method for converting&str
toString
).But what about
PyRef::into_super
?Yes,
into_super
can also be used to get the super-superclass and beyond, but it consumes thePyRef<T>
by value which can be awkward in many cases. If you haveobj: PyRef<T>
and just want to call a quick method on the super-superclass, your only option is to doobj.into_super().into_super().some_method()
, which consumes yourPyRef<T>
. Now if you want to keep using the child class, hopefully you still have theBound<T>
around toborrow
anotherPyRef
(and incur the run-time borrow-checking overhead of doing so). This PR would instead let you instead doobj.as_super().as_super().some_method()
and then keep using the originalPyRef<T>
.That said, there is certainly still a place for the
into_super
method; callingas_super
requires someone to retain ownership of thePyRef<T>
, so you could not return the&PyRef<T::BaseType>
from a function; for this you would need to useinto_super
and returnPyRef<T::BaseType>
by value. But under this system, theas_super
andinto_super
methods have distinct purposes and idiomatic names that most Rust users will immediately recognize as doing the same thing but by-reference vs. by-value.Implementation
PyRef::as_super
andPyRefMut::as_super
methodsThe implementations of the new methods added in this PR (
PyRef::as_super
,PyRefMut::as_super
, and the privatePyRefMut::downgrade
helper function) could literally be replaced by calls tostd::mem::transmute
(or the equivalent pointer casts); however, I broke them up a bit to make the safety logic more clear. For example, thePyRef::as_super
method callsdowncast_unchecked::<U>
on the innerBound<T>
instead of just going straight fromPyRef<T>
toPyRef<U>
. The same was not done forPyRefMut::as_super
, however, since it would required transmuting a shared reference&Bound<U>
to a mutable reference&mut PyRefMut<U>
which might be fine but feels sketchy.AsRef
andAndMut
implsA nice side effect of adding the purpose-built
as_super
methods was the ability to simplify the general-purposeAsRef<U>
andAsMut<U>
impls by simply re-borrowing the reference returned by theas_super
call. This avoid the need for additionalunsafe
code in these impls. However, doing this forPyRefMut
'sAsRef
impl required "downgrading" thePyRefMut
intoPyRef
, which I encapsulated in a privatePyRefMut::downgrade
associated function.Safety
The safety logic behind this PR is fairly simple, and can be summarized as the following:
#[repr(transparent)]
to thePyRef
/PyRefMut
structs such that they are guaranteed to have the same layout as theBound<T>
they wrap. By extension,PyRef<T>
andPyRefMut<T>
also have the same layout as each other.Bound<T>
has the same layout asBound<T::BaseType>
and can be transmuted to it freely (this is essentially howdowncast_unchecked
works, after all).PyRef
/PyRefMut
, so the differences inDrop
impls are not relevant; transmutingPyRefMut<T>
toPyRef<T>
would be problematic since dropping it would callrelease_borrow
instead ofrelease_borrow_mut
, but dropping references does not calldrop
so transmuting&PyRefMut<T>
to&PyRef<T>
is fine.As a result, the following pointer cast chains should be sound:
&PyRef<T>
->&Bound<T>
->&Bound<U>
->&PyRef<U>
(whereT: PyClass<BaseType=U>, U: PyClass
)&PyRefMut<T>
->&PyRef<T>
->&Bound<T>
->&Bound<U>
->&PyRef<U>
(whereT: PyClass<Frozen=False, BaseType=U>, U: PyClass
)&mut PyRefMut<T>
->&mut Bound<T>
->&mut Bound<U>
->&mut PyRefMut<U>
(whereT: PyClass<Frozen=False, BaseType=U>, U: PyClass<Frozen=False>
)