Rename PyClassShell with PyCell and do mutability checking#770
Rename PyClassShell with PyCell and do mutability checking#770kngwyu merged 29 commits intoPyO3:masterfrom
PyCell and do mutability checking#770Conversation
|
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... |
davidhewitt
left a comment
There was a problem hiding this comment.
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.
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.
This is already not Send nor Sync.
What kind of assertion do you have in mind?
There was a problem hiding this comment.
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?
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 |
davidhewitt
left a comment
There was a problem hiding this comment.
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.
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.
Alexander-N
left a comment
There was a problem hiding this comment.
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.
| We can get `&PyCell<T>`, not `PyCell<T>`. | |
| We can only get `&PyCell<T>`, not `PyCell<T>`. |
guide/src/class.md
Outdated
| 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.
| 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.
For me this is surprising, while get_super was very clear.
There was a problem hiding this comment.
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
|
|
||
| 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.
I find it a bit confusing that only into_super was used in the example.
There was a problem hiding this comment.
Oops, this was left unchanged... thanks
guide/src/class.md
Outdated
| - Use `self_: &PyCell<Self>` instead of `self`, and call `get_super()` | ||
| - `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.
Maybe we can simply remove get_base since I remember there was a problem with this method #381
| 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.
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.
They are comments for developers.
I rewrote them as doc comments.
|
@Alexander-N |
Resolves #342 #749 #738
TODO
try_borrow_unguardedso fix them to usetry_borrowPy::newTL; DR
Now we can take multiple mutable references of
PyClass.This PR fixes it by runtime reference counting like RefCell.
We can use
PyCelllike RefCell. We can getPyRefandPyRefMutfrom&PyCell, with interior mutability checking.The implementation is done via overhauling our
PyClassandPyClassShell, resulting in renamingPyClassShellwithPyCell.Notation
BorrowFlagit the reference counter thatPyCellhas. It can also represent mutably borrowed.Implementation
So, what's the problem of implementing mutability checking for
PyClasses?The answer is inheritation.
Our PyClass can inherit other class by
#[pyclass(extends=OtherClass)]syntax.In such cases, we should share
BorrowFlagbetween child and parent classes.So the following object layout is required:
It is somewhat difficult since
PyCellis defined recursively using the layout of base object, but I resolved this by usingas the farthest baseclasses for all
PyClasses.This needs some helper types like
derive_utils::PyBaseTypeUtils.Changed APIs
PyTryFrom: Now returns&PyCell<T>instead of&Tand&mut TFromPyPointer: Same as above.AsPyRef: Now has an associated typeTarget. Returns&PyCell<T>fromPy<T>.(TODO: make the complete list)
New APIs
PyRefPyRefMutPyDowncastImpl(TODO: make the complete list)