Skip to content

Commit bd4082a

Browse files
committed
Update class.md and Change super API
1 parent 2fd2185 commit bd4082a

File tree

3 files changed

+93
-44
lines changed

3 files changed

+93
-44
lines changed

guide/src/class.md

+59-38
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::PyCell<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,58 @@ 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` is our primary interface for that.
110115

111-
For getting *GIL-bounded* (i.e., with `'py` lifetime) references of `pyclass`,
112-
you can use `PyCell<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 get `&PyCell<T>`, not `PyCell<T>`.
114118

115-
### `PyCell`
116-
`PyCell` represents the actual layout of `pyclass` on the Python heap.
119+
Thus, to mutate data behind `&PyCell` safely, we employs
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 `PyCell::new_ref` or `PyCell::new_mut`.
123+
Users who are familiar with `RefCell` can use `PyCell` just like `RefCell`.
124+
125+
For users who doesn't know `RefCell` well, we repeat the Rust's borrowing rule here:
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 managing a reference counter.
129+
130+
TODO: link to the API document
120131

121132
```rust
122133
# use pyo3::prelude::*;
123134
# use pyo3::types::PyDict;
124135
# use pyo3::PyCell;
125136
#[pyclass]
126137
struct MyClass {
138+
#[pyo3(get)]
127139
num: i32,
128140
debug: bool,
129141
}
130142
let gil = Python::acquire_gil();
131143
let py = gil.python();
132-
let obj = PyCell::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 `&PyCell` as a normal Python object
137-
dict.set_item("obj", obj).unwrap();
138-
139-
// return &mut PyCell<MyClass>
140-
let obj = PyCell::new_mut(py, MyClass { num: 3, debug: true }).unwrap();
141-
obj.num = 5;
144+
let obj = PyCell::new(py, MyClass { num: 3, debug: true }).unwrap();
145+
{
146+
let obj_ref = obj.borrow(); // Get PyRef
147+
assert_eq!(obj_ref.num, 3);
148+
// You cannot get PyRefMut unless all PyRef drop
149+
assert!(obj.try_borrow_mut().is_err());
150+
}
151+
{
152+
let mut obj_mut = obj.borrow_mut(); // Get PyRefMut
153+
obj_mut.num = 5;
154+
// You cannot get PyRef unless all PyRefMut drop
155+
assert!(obj.try_borrow().is_err());
156+
}
157+
// You can convert `&PyCell` to Python object
158+
pyo3::py_run!(py, obj, "assert obj.num == 5")
142159
```
143160

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.
161+
`&PyCell<T>` is bouded by the same lifetime as `GILGuard`.
162+
To avoid this you can use `Py<T>`, which stores an object longer than the GIL lifetime.
149163
```rust
150164
# use pyo3::prelude::*;
151165
#[pyclass]
@@ -159,7 +173,9 @@ fn return_myclass() -> Py<MyClass> {
159173
}
160174
let gil = Python::acquire_gil();
161175
let obj = return_myclass();
162-
assert_eq!(obj.as_ref(gil.python()).num, 1);
176+
let cell = obj.as_ref(gil.python()); // AsPyRef::as_ref returns &PyCell
177+
let obj_ref = cell.borrow(); // Get PyRef<T>
178+
assert_eq!(obj_ref.num, 1);
163179
```
164180

165181
## Customizing the class
@@ -228,6 +244,9 @@ baseclass of `T`.
228244
But for more deeply nested inheritance, you have to return `PyClassInitializer<T>`
229245
explicitly.
230246

247+
To get a parent class from child, use `PyRef<T>` instead of `&self`,
248+
or `PyRefMut<T>` instead of `&mut self`.
249+
231250
```rust
232251
# use pyo3::prelude::*;
233252
use pyo3::PyCell;
@@ -261,8 +280,9 @@ impl SubClass {
261280
(SubClass{ val2: 15}, BaseClass::new())
262281
}
263282

264-
fn method2(self_: &PyCell<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_super(); // Get &BaseClass
285+
super_.method().map(|x| x * self_.val2)
266286
}
267287
}
268288

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

282-
fn method3(self_: &PyCell<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::PyCell::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

@@ -761,8 +782,8 @@ struct GCTracked {} // Fails because it does not implement PyGCProtocol
761782
Iterators can be defined using the
762783
[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait.
763784
It includes two methods `__iter__` and `__next__`:
764-
* `fn __iter__(slf: &mut PyCell<Self>) -> PyResult<impl IntoPy<PyObject>>`
765-
* `fn __next__(slf: &mut PyCell<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
785+
* `fn __iter__(slf: PyRefMut<Self>) -> PyResult<impl IntoPy<PyObject>>`
786+
* `fn __next__(slf: PyRefMut<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
766787

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

@@ -779,10 +800,10 @@ struct MyIterator {
779800

780801
#[pyproto]
781802
impl PyIterProtocol for MyIterator {
782-
fn __iter__(slf: &mut PyCell<Self>) -> PyResult<Py<MyIterator>> {
803+
fn __iter__(mut slf: PyRefMut<Self>) -> PyResult<Py<MyIterator>> {
783804
Ok(slf.into())
784805
}
785-
fn __next__(slf: &mut PyCell<Self>) -> PyResult<Option<PyObject>> {
806+
fn __next__(mut slf: PyRefMut<Self>) -> PyResult<Option<PyObject>> {
786807
Ok(slf.iter.next())
787808
}
788809
}

guide/src/python_from_rust.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ let userdata = UserData {
6161
id: 34,
6262
name: "Yu".to_string(),
6363
};
64-
let userdata = PyCell::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)"

src/pycell.rs

+33-5
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,29 @@ pub struct PyRef<'p, T: PyClass> {
258258
inner: &'p PyCellInner<T>,
259259
}
260260

261-
impl<'p, T: PyClass> PyRef<'p, T> {
262-
pub fn get_super(&'p self) -> &'p T::BaseType {
261+
impl<'p, T> PyRef<'p, T>
262+
where
263+
T: PyClass,
264+
{
265+
pub fn as_super(&'p self) -> &'p T::BaseType {
263266
unsafe { self.inner.ob_base.unchecked_ref() }
264267
}
265268
}
266269

270+
impl<'p, T, U> PyRef<'p, T>
271+
where
272+
T: PyClass + PyTypeInfo<BaseType = U, BaseLayout = PyCellInner<U>>,
273+
U: PyClass,
274+
{
275+
pub fn into_super(self) -> PyRef<'p, U> {
276+
let res = PyRef {
277+
inner: &self.inner.ob_base,
278+
};
279+
std::mem::forget(self); // Avoid drop
280+
res
281+
}
282+
}
283+
267284
impl<'p, T: PyClass> Deref for PyRef<'p, T> {
268285
type Target = T;
269286

@@ -310,11 +327,22 @@ pub struct PyRefMut<'p, T: PyClass> {
310327
}
311328

312329
impl<'p, T: PyClass> PyRefMut<'p, T> {
313-
pub fn get_super(&'p self) -> &'p T::BaseType {
330+
pub fn as_super(&'p self) -> &'p T::BaseType {
314331
unsafe { self.inner.ob_base.unchecked_ref() }
315332
}
316-
pub fn get_super_mut(&'p self) -> &'p mut T::BaseType {
317-
unsafe { self.inner.ob_base.unchecked_refmut() }
333+
}
334+
335+
impl<'p, T, U> PyRefMut<'p, T>
336+
where
337+
T: PyClass + PyTypeInfo<BaseType = U, BaseLayout = PyCellInner<U>>,
338+
U: PyClass,
339+
{
340+
pub fn into_super(self) -> PyRefMut<'p, U> {
341+
let res = PyRefMut {
342+
inner: &self.inner.ob_base,
343+
};
344+
std::mem::forget(self); // Avoid drop
345+
res
318346
}
319347
}
320348

0 commit comments

Comments
 (0)