-
Notifications
You must be signed in to change notification settings - Fork 770
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
Rename PyClassShell with PyCell
and do mutability checking
#770
Conversation
Awesome! There's a lot here and I'll try to give it all a thorough review, though this will take me a few days... |
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 is awesome stuff, really excited to see this! I've given this a really deep read and asked a lot of questions - hopefully useful comments.
In such cases, we should share BorrowFlag between child and parent classes.
Can you explain in a bit more detail why this has to be the case? I would think that as the Rust objects are separate, it should be safe to have &mut T::Base
and &T
simultaneously.
#[repr(C)] | ||
pub struct PyCellBase<T: PyTypeInfo> { | ||
ob_base: T::Layout, | ||
borrow_flag: Cell<BorrowFlag>, |
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.
Is it worth making an assertion that this doesn't need to be thread-safe (i.e. not AtomicXyz
) because the of the GIL?
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 is already not Send
nor Sync
.
What kind of assertion do you have in mind?
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.
Even if it's not Send
or Sync
in Rust, the python interpreter will happily share this data between threads. Perhaps just a comment explaining why Cell
is 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.
Co-Authored-By: David Hewitt <1939362+davidhewitt@users.noreply.github.com>
Thank you for the lots of reviews!
If we allow having impl<T> PyCell<T> {
pub fn get_super(&self) -> &PyCell<T::BaseType> { ... }
}
impl<'p, T> PyRef<'p, T>
{
pub fn get_super(&self) -> &PyCell<T::BaseType> { ... }
}
... I think this has some downsides:
|
Ah, that makes sense, thanks. Let's see what the gitter thread prefers. I'm warming to your design. It would be strange to try to |
An idea: what if we had a type Then the API for
Along with:
I wrote this in a rush, sorry if it doesn't make much sense, or it has other problems! |
Sounds good for the future design, but I don't think we should add it now. |
Excited to see all the progress on this. I'm hoping to find time to give this another review in the next day or two. 👍 |
I can't contribute at the moment, but just wanted to say that this is very cool. Making the API safe is very much appreciated! |
@Alexander-N |
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've managed to give this another read through. Really pleased with how this is shaping up!
Given the similarity between PyCellBase
and RefCell
, I wonder if we could simplify the code and make PyCell
a wrapper around RefCell
rather than implementing the unsafe
primitives ourselves. I appreciate the way this is laid out it might be hard, but in my opinion if we can do it and reduce the amount of unsafe
non-ffi code in pyo3
, it's worth it.
Co-Authored-By: David Hewitt <1939362+davidhewitt@users.noreply.github.com>
@davidhewitt
|
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.
Few small tidy-ups still possible; in general this is looking great to me.
RefCell is a pair (T, BorrowFlag).
However, to enable extends= feature, PyCell can be a triplet (Base, Sub, BorrowFlag) or larger tuple. This nature conceptually makes it difficult to use RefCell here.
That makes total sense. Let's keep it as-is.
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 seems like a great step, very excited to see this! At the moment I can't look at it in detail, but I tried to at least give some feedback, hope it helps.
guide/src/class.md
Outdated
you can use `PyClassShell<T>`. | ||
Or you can use `Py<T>` directly, for *not-GIL-bounded* references. | ||
`PyCell<T: PyClass>` is always allocated in the Python heap, so we don't have the ownership of it. | ||
We can get `&PyCell<T>`, not `PyCell<T>`. |
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 get `&PyCell<T>`, not `PyCell<T>`. | |
We can only get `&PyCell<T>`, not `PyCell<T>`. |
guide/src/class.md
Outdated
@@ -228,9 +242,12 @@ baseclass of `T`. | |||
But for more deeply nested inheritance, you have to return `PyClassInitializer<T>` | |||
explicitly. | |||
|
|||
To get a parent class from child, use `PyRef<T>` instead of `&self`, |
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.
To get a parent class from child, use `PyRef<T>` instead of `&self`, | |
To get a parent class from a child, use `PyRef<T>` instead of `&self`, |
fn method2(self_: &PyClassShell<Self>) -> PyResult<usize> { | ||
self_.get_super().method().map(|x| x * self_.val2) | ||
fn method2(self_: PyRef<Self>) -> PyResult<usize> { | ||
let super_ = self_.as_ref(); // Get &BaseClass |
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.
For me this is surprising, while get_super
was very clear.
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.
It was initially named as_super
but changed to use AsRef
since wasm-bindgen does so, after some discussions in gitter.
I prefer to as_super
but, in the long run, it would be beneficial to use the same API convention with a major library.
guide/src/class.md
Outdated
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000") | ||
``` | ||
|
||
To access the super class, you can use either of these two ways: | ||
- Use `self_: &PyClassShell<Self>` instead of `self`, and call `get_super()` | ||
- Use `self_: &PyCell<Self>` instead of `self`, and call `get_super()` |
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 find it a bit confusing that only into_super
was used in the example.
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.
Oops, this was left unchanged... thanks
guide/src/class.md
Outdated
- `ObjectProtocol::get_base` | ||
We recommend `PyClassShell` here, since it makes the context much clearer. | ||
We recommend `PyCell` here, since it makes the context much clearer. |
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.
Maybe we can simply remove get_base
since I remember there was a problem with this method #381
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.
👍
@@ -761,16 +780,16 @@ struct GCTracked {} // Fails because it does not implement PyGCProtocol | |||
Iterators can be defined using the | |||
[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait. | |||
It includes two methods `__iter__` and `__next__`: | |||
* `fn __iter__(slf: &mut PyClassShell<Self>) -> PyResult<impl IntoPy<PyObject>>` | |||
* `fn __next__(slf: &mut PyClassShell<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>` | |||
* `fn __iter__(slf: PyRefMut<Self>) -> PyResult<impl IntoPy<PyObject>>` |
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 liked that PyRefMut was replaced with PyClassShell since I find PyRef/PyRefMut unintuitive for reasons mentioned in #356
pyo3-derive-backend/src/method.rs
Outdated
PySelf(syn::TypeReference), | ||
// self_: &PyCell<Self>, | ||
PySelfRef(syn::TypeReference), | ||
// self_: PyRef<Self> or PyRefMut<Self> |
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.
Forgotten comments?
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.
They are comments for developers.
I rewrote them as doc comments.
@Alexander-N |
Resolves #342 #749 #738
TODO
try_borrow_unguarded
so fix them to usetry_borrow
Py::new
TL; DR
Now we can take multiple mutable references of
PyClass
.This PR fixes it by runtime reference counting like RefCell.
We can use
PyCell
like RefCell. We can getPyRef
andPyRefMut
from&PyCell
, with interior mutability checking.The implementation is done via overhauling our
PyClass
andPyClassShell
, resulting in renamingPyClassShell
withPyCell
.Notation
BorrowFlag
it the reference counter thatPyCell
has. It can also represent mutably borrowed.Implementation
So, what's the problem of implementing mutability checking for
PyClass
es?The answer is inheritation.
Our PyClass can inherit other class by
#[pyclass(extends=OtherClass)]
syntax.In such cases, we should share
BorrowFlag
between child and parent classes.So the following object layout is required:
It is somewhat difficult since
PyCell
is defined recursively using the layout of base object, but I resolved this by usingas the farthest baseclasses for all
PyClass
es.This needs some helper types like
derive_utils::PyBaseTypeUtils
.Changed APIs
PyTryFrom
: Now returns&PyCell<T>
instead of&T
and&mut T
FromPyPointer
: Same as above.AsPyRef
: Now has an associated typeTarget
. Returns&PyCell<T>
fromPy<T>
.(TODO: make the complete list)
New APIs
PyRef
PyRefMut
PyDowncastImpl
(TODO: make the complete list)