Skip to content

Commit fee755a

Browse files
authored
Merge pull request #770 from kngwyu/pycell
Rename PyClassShell with `PyCell` and do mutability checking
2 parents 3ded0b2 + ee0c178 commit fee755a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1794
-1171
lines changed

CHANGELOG.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1616
* The implementation for `IntoPy<U> for T` where `U: FromPy<T>` is no longer specializable. Control the behavior of this via the implementation of `FromPy`. [#713](https://github.com/PyO3/pyo3/pull/713)
1717
* Use `parking_lot::Mutex` instead of `spin::Mutex`. [#734](https://github.com/PyO3/pyo3/pull/734)
1818
* Bumped minimum Rust version to `1.42.0-nightly 2020-01-21`. [#761](https://github.com/PyO3/pyo3/pull/761)
19+
* `PyRef` and `PyRefMut` are renewed for `PyCell`. [#770](https://github.com/PyO3/pyo3/pull/770)
1920

2021
### Added
21-
22-
* `PyClass`, `PyClassShell`, `PyObjectLayout`, `PyClassInitializer`. [#683](https://github.com/PyO3/pyo3/pull/683)
22+
* `PyCell`, which has RefCell-like features. [#770](https://github.com/PyO3/pyo3/pull/770)
23+
* `PyClass`, `PyLayout`, `PyClassInitializer`. [#683](https://github.com/PyO3/pyo3/pull/683)
2324
* Implemented `IntoIterator` for `PySet` and `PyFrozenSet`. [#716](https://github.com/PyO3/pyo3/pull/716)
2425
* `FromPyObject` is now automatically implemented for `T: Clone` pyclasses. [#730](https://github.com/PyO3/pyo3/pull/730)
2526
* `#[pyo3(get)]` and `#[pyo3(set)]` will now use the Rust doc-comment from the field for the Python property. [#755](https://github.com/PyO3/pyo3/pull/755)
2627
* `#[setter]` functions may now take an argument of `Pyo3::Python`. [#760](https://github.com/PyO3/pyo3/pull/760)
28+
* `PyTypeInfo::BaseLayout` and `PyClass::BaseNativeType`. [#770](https://github.com/PyO3/pyo3/pull/770)
29+
* `PyDowncastImpl`. [#770](https://github.com/PyO3/pyo3/pull/770)
2730

2831
### Fixed
2932

@@ -35,9 +38,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3538

3639
### Removed
3740

38-
* `PyRef`, `PyRefMut`, `PyRawObject`. [#683](https://github.com/PyO3/pyo3/pull/683)
41+
* `PyRawObject`. [#683](https://github.com/PyO3/pyo3/pull/683)
3942
* `PyNoArgsFunction`. [#741](https://github.com/PyO3/pyo3/pull/741)
4043
* `initialize_type()`. To set the module name for a `#[pyclass]`, use the `module` argument to the macro. #[751](https://github.com/PyO3/pyo3/pull/751)
44+
* `AsPyRef::as_mut/with/with_mut/into_py/into_mut_py`. [#770](https://github.com/PyO3/pyo3/pull/770)
45+
* `PyTryFrom::try_from_mut/try_from_mut_exact/try_from_mut_unchecked`. [#770](https://github.com/PyO3/pyo3/pull/770)
46+
* `Python::mut_from_owned_ptr/mut_from_borrowed_ptr`. [#770](https://github.com/PyO3/pyo3/pull/770)
47+
* `ObjectProtocol::get_base/get_mut_base`. [#770](https://github.com/PyO3/pyo3/pull/770)
4148

4249
## [0.8.5]
4350

guide/src/class.md

Lines changed: 63 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Specifically, the following implementation is generated:
2222

2323
```rust
2424
use pyo3::prelude::*;
25+
use pyo3::types::PyAny;
2526

2627
/// Class for demonstration
2728
struct MyClass {
@@ -33,9 +34,10 @@ impl pyo3::pyclass::PyClassAlloc for MyClass {}
3334

3435
unsafe impl pyo3::PyTypeInfo for MyClass {
3536
type Type = MyClass;
36-
type BaseType = pyo3::types::PyAny;
37-
type ConcreteLayout = pyo3::PyClassShell<Self>;
38-
type Initializer = pyo3::PyClassInitializer<Self>;
37+
type BaseType = PyAny;
38+
type BaseLayout = pyo3::pycell::PyCellBase<PyAny>;
39+
type Layout = PyCell<Self>;
40+
type Initializer = PyClassInitializer<Self>;
3941

4042
const NAME: &'static str = "MyClass";
4143
const MODULE: Option<&'static str> = None;
@@ -53,6 +55,7 @@ unsafe impl pyo3::PyTypeInfo for MyClass {
5355
impl pyo3::pyclass::PyClass for MyClass {
5456
type Dict = pyo3::pyclass_slots::PyClassDummySlot;
5557
type WeakRef = pyo3::pyclass_slots::PyClassDummySlot;
58+
type BaseNativeType = PyAny;
5659
}
5760

5861
impl pyo3::IntoPy<PyObject> for MyClass {
@@ -105,47 +108,56 @@ fn mymodule(_py: Python, m: &PyModule) -> PyResult<()> {
105108
}
106109
```
107110

108-
## Get Python objects from `pyclass`
109-
You sometimes need to convert your `pyclass` into a Python object in Rust code (e.g., for testing it).
111+
## PyCell and interior mutability
112+
You sometimes need to convert your `pyclass` into a Python object and access it
113+
from Rust code (e.g., for testing it).
114+
[`PyCell`](https://pyo3.rs/master/doc/pyo3/pycell/struct.PyCell.html) is our primary interface for that.
110115

111-
For getting *GIL-bounded* (i.e., with `'py` lifetime) references of `pyclass`,
112-
you can use `PyClassShell<T>`.
113-
Or you can use `Py<T>` directly, for *not-GIL-bounded* references.
116+
`PyCell<T: PyClass>` is always allocated in the Python heap, so we don't have the ownership of it.
117+
We can only get `&PyCell<T>`, not `PyCell<T>`.
114118

115-
### `PyClassShell`
116-
`PyClassShell` represents the actual layout of `pyclass` on the Python heap.
119+
Thus, to mutate data behind `&PyCell` safely, we employ the
120+
[Interior Mutability Pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html)
121+
like [std::cell::RefCell](https://doc.rust-lang.org/std/cell/struct.RefCell.html).
117122

118-
If you want to instantiate `pyclass` in Python and get the reference,
119-
you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`.
123+
Users who are familiar with `RefCell` can use `PyCell` just like `RefCell`.
124+
125+
For users who are not very familiar with `RefCell`, here is a reminder of Rust's rules of borrowing:
126+
- At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.
127+
- References must always be valid.
128+
`PyCell` ensures these borrowing rules by tracking references at runtime.
120129

121130
```rust
122131
# use pyo3::prelude::*;
123132
# use pyo3::types::PyDict;
124-
# use pyo3::PyClassShell;
125133
#[pyclass]
126134
struct MyClass {
135+
#[pyo3(get)]
127136
num: i32,
128137
debug: bool,
129138
}
130139
let gil = Python::acquire_gil();
131140
let py = gil.python();
132-
let obj = PyClassShell::new_ref(py, MyClass { num: 3, debug: true }).unwrap();
133-
// You can use deref
134-
assert_eq!(obj.num, 3);
135-
let dict = PyDict::new(py);
136-
// You can treat a `&PyClassShell` as a normal Python object
137-
dict.set_item("obj", obj).unwrap();
138-
139-
// return &mut PyClassShell<MyClass>
140-
let obj = PyClassShell::new_mut(py, MyClass { num: 3, debug: true }).unwrap();
141-
obj.num = 5;
141+
let obj = PyCell::new(py, MyClass { num: 3, debug: true }).unwrap();
142+
{
143+
let obj_ref = obj.borrow(); // Get PyRef
144+
assert_eq!(obj_ref.num, 3);
145+
// You cannot get PyRefMut unless all PyRefs are dropped
146+
assert!(obj.try_borrow_mut().is_err());
147+
}
148+
{
149+
let mut obj_mut = obj.borrow_mut(); // Get PyRefMut
150+
obj_mut.num = 5;
151+
// You cannot get any other refs until the PyRefMut is dropped
152+
assert!(obj.try_borrow().is_err());
153+
assert!(obj.try_borrow_mut().is_err());
154+
}
155+
// You can convert `&PyCell` to Python object
156+
pyo3::py_run!(py, obj, "assert obj.num == 5")
142157
```
143158

144-
### `Py`
145-
146-
`Py` is an object wrapper which stores an object longer than the GIL lifetime.
147-
148-
You can use it to avoid lifetime problems.
159+
`&PyCell<T>` is bounded by the same lifetime as `GILGuard`.
160+
To avoid this you can use `Py<T>`, which stores an object longer than the GIL lifetime.
149161
```rust
150162
# use pyo3::prelude::*;
151163
#[pyclass]
@@ -159,7 +171,9 @@ fn return_myclass() -> Py<MyClass> {
159171
}
160172
let gil = Python::acquire_gil();
161173
let obj = return_myclass();
162-
assert_eq!(obj.as_ref(gil.python()).num, 1);
174+
let cell = obj.as_ref(gil.python()); // AsPyRef::as_ref returns &PyCell
175+
let obj_ref = cell.borrow(); // Get PyRef<T>
176+
assert_eq!(obj_ref.num, 1);
163177
```
164178

165179
## Customizing the class
@@ -228,9 +242,14 @@ baseclass of `T`.
228242
But for more deeply nested inheritance, you have to return `PyClassInitializer<T>`
229243
explicitly.
230244

245+
To get a parent class from a child, use `PyRef<T>` instead of `&self`,
246+
or `PyRefMut<T>` instead of `&mut self`.
247+
Then you can access a parent class by `self_.as_ref()` as `&Self::BaseClass`,
248+
or by `self_.into_super()` as `PyRef<Self::BaseClass>`.
249+
231250
```rust
232251
# use pyo3::prelude::*;
233-
use pyo3::PyClassShell;
252+
use pyo3::PyCell;
234253

235254
#[pyclass]
236255
struct BaseClass {
@@ -261,8 +280,9 @@ impl SubClass {
261280
(SubClass{ val2: 15}, BaseClass::new())
262281
}
263282

264-
fn method2(self_: &PyClassShell<Self>) -> PyResult<usize> {
265-
self_.get_super().method().map(|x| x * self_.val2)
283+
fn method2(self_: PyRef<Self>) -> PyResult<usize> {
284+
let super_ = self_.as_ref(); // Get &BaseClass
285+
super_.method().map(|x| x * self_.val2)
266286
}
267287
}
268288

@@ -279,29 +299,24 @@ impl SubSubClass {
279299
.add_subclass(SubSubClass{val3: 20})
280300
}
281301

282-
fn method3(self_: &PyClassShell<Self>) -> PyResult<usize> {
283-
let super_ = self_.get_super();
284-
SubClass::method2(super_).map(|x| x * self_.val3)
302+
fn method3(self_: PyRef<Self>) -> PyResult<usize> {
303+
let v = self_.val3;
304+
let super_ = self_.into_super(); // Get PyRef<SubClass>
305+
SubClass::method2(super_).map(|x| x * v)
285306
}
286307
}
287308

288309

289310
# let gil = Python::acquire_gil();
290311
# let py = gil.python();
291-
# let subsub = pyo3::PyClassShell::new_ref(py, SubSubClass::new()).unwrap();
312+
# let subsub = pyo3::PyCell::new(py, SubSubClass::new()).unwrap();
292313
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000")
293314
```
294315

295-
To access the super class, you can use either of these two ways:
296-
- Use `self_: &PyClassShell<Self>` instead of `self`, and call `get_super()`
297-
- `ObjectProtocol::get_base`
298-
We recommend `PyClassShell` here, since it makes the context much clearer.
299-
300-
301316
If `SubClass` does not provide a baseclass initialization, the compilation fails.
302317
```compile_fail
303318
# use pyo3::prelude::*;
304-
use pyo3::PyClassShell;
319+
use pyo3::PyCell;
305320
306321
#[pyclass]
307322
struct BaseClass {
@@ -761,16 +776,16 @@ struct GCTracked {} // Fails because it does not implement PyGCProtocol
761776
Iterators can be defined using the
762777
[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait.
763778
It includes two methods `__iter__` and `__next__`:
764-
* `fn __iter__(slf: &mut PyClassShell<Self>) -> PyResult<impl IntoPy<PyObject>>`
765-
* `fn __next__(slf: &mut PyClassShell<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
779+
* `fn __iter__(slf: PyRefMut<Self>) -> PyResult<impl IntoPy<PyObject>>`
780+
* `fn __next__(slf: PyRefMut<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
766781

767782
Returning `Ok(None)` from `__next__` indicates that that there are no further items.
768783

769784
Example:
770785

771786
```rust
772787
use pyo3::prelude::*;
773-
use pyo3::{PyIterProtocol, PyClassShell};
788+
use pyo3::{PyIterProtocol, PyCell};
774789

775790
#[pyclass]
776791
struct MyIterator {
@@ -779,19 +794,15 @@ struct MyIterator {
779794

780795
#[pyproto]
781796
impl PyIterProtocol for MyIterator {
782-
fn __iter__(slf: &mut PyClassShell<Self>) -> PyResult<Py<MyIterator>> {
797+
fn __iter__(mut slf: PyRefMut<Self>) -> PyResult<Py<MyIterator>> {
783798
Ok(slf.into())
784799
}
785-
fn __next__(slf: &mut PyClassShell<Self>) -> PyResult<Option<PyObject>> {
800+
fn __next__(mut slf: PyRefMut<Self>) -> PyResult<Option<PyObject>> {
786801
Ok(slf.iter.next())
787802
}
788803
}
789804
```
790805

791-
## Manually implementing pyclass
792-
793-
TODO: Which traits to implement (basically `PyTypeCreate: PyObjectAlloc + PyTypeInfo + PyMethodsProtocol + Sized`) and what they mean.
794-
795806
## How methods are implemented
796807

797808
Users should be able to define a `#[pyclass]` with or without `#[pymethods]`, while PyO3 needs a

guide/src/python_from_rust.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ your Python extensions quickly.
3636

3737
```rust
3838
use pyo3::prelude::*;
39-
use pyo3::{PyClassShell, PyObjectProtocol, py_run};
39+
use pyo3::{PyCell, PyObjectProtocol, py_run};
4040
# fn main() {
4141
#[pyclass]
4242
struct UserData {
@@ -61,7 +61,7 @@ let userdata = UserData {
6161
id: 34,
6262
name: "Yu".to_string(),
6363
};
64-
let userdata = PyClassShell::new_ref(py, userdata).unwrap();
64+
let userdata = PyCell::new(py, userdata).unwrap();
6565
let userdata_as_tuple = (34, "Yu");
6666
py_run!(py, userdata userdata_as_tuple, r#"
6767
assert repr(userdata) == "User Yu(id: 34)"

0 commit comments

Comments
 (0)