From f9a5f7ea23e5dd167b1bbabbdc268d4568bf32c2 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 7 Mar 2020 18:13:15 +0900 Subject: [PATCH] Write migration guide for 0.9 --- guide/src/SUMMARY.md | 3 +- guide/src/migration.md | 183 ++++++++++++++++++++++++++++++++++++++ guide/src/rust_cpython.md | 2 +- src/lib.rs | 1 + 4 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 guide/src/migration.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index dc0c843dfbb..68fa8c158bb 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -12,4 +12,5 @@ - [Advanced Topics](advanced.md) - [Building and Distribution](building_and_distribution.md) - [PyPy support](pypy.md) -- [Appendix: PyO3 and rust-cpython](rust_cpython.md) +- [Appendix A: PyO3 and rust-cpython](rust_cpython.md) +- [Appendix B: Migration Guide](migration.md) diff --git a/guide/src/migration.md b/guide/src/migration.md new file mode 100644 index 00000000000..0f851788618 --- /dev/null +++ b/guide/src/migration.md @@ -0,0 +1,183 @@ +# Appendix B: Migration Guides for major version changes + +## from 0.8.* to 0.9 + +### `#[new]` interface +[`PyRawObject`](https://docs.rs/pyo3/0.8.5/pyo3/type_object/struct.PyRawObject.html) +is now removed and our syntax for constructor changed. + +Before: +```compile_fail +#[pyclass] +struct MyClass {} + +#[pymethods] +impl MyClass { + #[new] + fn new(obj: &PyRawObject) { + obj.init(MyClass { }) + } +} +``` + +After: +``` +# use pyo3::prelude::*; +#[pyclass] +struct MyClass {} + +#[pymethods] +impl MyClass { + #[new] + fn new() -> Self { + MyClass {} + } +} +``` + +Basically you can return `Self` or `Result` directly. +For more, see [the constructor section](https://pyo3.rs/master/class.html#constructor) of this guide. + +### PyCell +PyO3 0.9 introduces [`PyCell`](https://pyo3.rs/master/doc/pyo3/pycell/struct.PyCell.html), which is +a [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) like object wrapper +for dynamically ensuring +[Rust's rule of Reference](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#the-rules-of-references). + +For `#[pymethods]` or `#[pyfunction]`s, `PyCell` works without any change. +Just throw errors when there happens invalid borrowing. + +Here is an example. +``` +# use pyo3::prelude::*; +#[pyclass] +struct Names { + names: Vec +} + +#[pymethods] +impl Names { + #[new] + fn new() -> Self { + Names { names: vec![] } + } + fn merge(&mut self, other: &mut Names) { + self.names.append(&mut other.names) + } +} +# let gil = Python::acquire_gil(); +# let py = gil.python(); +# let names = PyCell::new(py, Names::new()).unwrap(); +# let borrow_mut_err = py.get_type::(); +# pyo3::py_run!(py, names borrow_mut_err, r" +# try: +# names.merge(names) +# assert False, 'Unreachable' +# except Exception as e: +# isinstance(e, borrow_mut_err) +# "); +``` +`Names` has `merge` method, which takes `&mut self` and `&mut Self`. +Given this `#[pyclass]`, calling `names.merge(names)` in Python raises `PyBorrowMutError` exception, +since it requires two mutable borrows of `names`, + +However, for `#[pyproto]` and some functions, you need to manually fix codes. + +#### Object creation +We could use the older `PyRef` and `PyRefMut` for object creation, but now they are just +reference wrappers for `PyCell`. +Use `PyCell::new` instead. + +Before: +```compile_fail +# use pyo3::prelude::*; +# #[pyclass] +# struct MyClass {} +let gil = Python::acquire_gil(); +let py = gil.python(); +let obj_ref = PyRef::new(py, MyClass {}).unwrap(); +``` + +After: +``` +# use pyo3::prelude::*; +# #[pyclass] +# struct MyClass {} +let gil = Python::acquire_gil(); +let py = gil.python(); +let obj = PyCell::new(py, MyClass {}).unwrap(); +let obj_ref = obj.borrow(); +``` + +#### Object extraction +Now for `T: PyClass`, `&T` and `&mut T` don't have `FromPyObject` implementation. +Instead, you can use `&PyCell`, `PyRef`, and `PyRefMut` for object extraction. + +Before: +```ignore +let obj: &PyAny = create_obj(); +let obj_ref: &MyClass = obj.extract().unwrap(); +let obj_ref_mut: &mut MyClass = obj.extract().unwrap(); +``` + +After: +``` +# use pyo3::prelude::*; +# use pyo3::types::{PyAny, IntoPyDict}; +# #[pyclass] struct MyClass {} +# #[pymethods] impl MyClass { #[new]fn new() -> Self { MyClass {} }} +# let gil = Python::acquire_gil(); +# let py = gil.python(); +# let typeobj = py.get_type::(); +# let d = [("c", typeobj)].into_py_dict(py); +# let create_obj = || py.eval("c()", None, Some(d)).unwrap(); +let obj: &PyAny = create_obj(); +let obj_cell: &PyCell = obj.extract().unwrap(); +{ + let obj_ref: PyRef = obj.extract().unwrap(); + // we need to drop obj_ref before taking RefMut +} +let obj_ref_mut: PyRefMut = obj.extract().unwrap(); +``` + + +#### `#[pyproto]` +Most of `#[pyproto]` arguments requires [`FromPyObject`] implementation. +So if your protocol methods take `&T` or `&mut T`(where `T: PyClass`), +please use `PyRef` or `PyRefMut` instead. + +Before: +```compile_fail +# use pyo3::prelude::*; +# use pyo3::class::PySequenceProtocol; +#[pyclass] +struct ByteSequence { + elements: Vec, +} +#[pyproto] +impl PySequenceProtocol for ByteSequence { + fn __concat__(&self, other: &Self) -> PyResult { + let mut elements = self.elements.clone(); + elements.extend_from_slice(&other.elements); + Ok(Self { elements }) + } +} +``` + +After: +``` +# use pyo3::prelude::*; +# use pyo3::class::PySequenceProtocol; +#[pyclass] +struct ByteSequence { + elements: Vec, +} +#[pyproto] +impl PySequenceProtocol for ByteSequence { + fn __concat__(&self, other: PyRef<'p, Self>) -> PyResult { + let mut elements = self.elements.clone(); + elements.extend_from_slice(&other.elements); + Ok(Self { elements }) + } +} +``` diff --git a/guide/src/rust_cpython.md b/guide/src/rust_cpython.md index ebb3fdaf604..d086e9e450c 100644 --- a/guide/src/rust_cpython.md +++ b/guide/src/rust_cpython.md @@ -1,4 +1,4 @@ -# Appendix: PyO3 and rust-cpython +# Appendix A: PyO3 and rust-cpython PyO3 began as fork of [rust-cpython](https://github.com/dgrunwald/rust-cpython) when rust-cpython wasn't maintained. Over the time PyO3 has become fundamentally different from rust-cpython. diff --git a/src/lib.rs b/src/lib.rs index 6f1f342e138..cbf06e95a37 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -328,4 +328,5 @@ pub mod doc_test { doctest!("../guide/src/parallelism.md", guide_parallelism_md); doctest!("../guide/src/pypy.md", guide_pypy_md); doctest!("../guide/src/rust_cpython.md", guide_rust_cpython_md); + doctest!("../guide/src/migration.md", guide_migration_md); }