diff --git a/CHANGES.txt b/CHANGES.txt index cb68fbdf..64389c96 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,8 @@ Changes 0.1.x (xx-xx-2017) ^^^^^^^^^^^^^^^^^^ +* Added weakref support #56 + * Allow to add gc support without implementing PyGCProtocol #57 diff --git a/guide/src/class.md b/guide/src/class.md index 6a80adff..bc2b7997 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -44,6 +44,23 @@ are generated only if struct contains `PyToken` attribute. TODO - continue +## py::class macro + +Python class generation is powered by [Procedural Macros](https://doc.rust-lang.org/book/first-edition/procedural-macros.html). +To define python custom class, rust struct needs to be annotated with `#[py::class]` attribute. +`py::class` macro accepts following parameters: + +* `name=XXX` - customize class name visible to python code. By default struct name is used as +a class name. +* `freelist=XXX` - `freelist` parameter add support of free allocation list to custom class. +The performance improvement applies to types that are often created and deleted in a row, +so that they can benefit from a freelist. `XXX` is a number of items for free list. +* `gc` - adds support for python garbage collector. classes that build with `gc` parameter +participate in python garbage collector. If custom class contains references to other +python object that can be collector `PyGCProtocol` trait has to be implemented. +* `weakref` - adds support for python weak references + + ## Constructor By default it is not possible to create instance of custom class from python code. diff --git a/pyo3cls/src/py_class.rs b/pyo3cls/src/py_class.rs index a80f9bcd..56d92ece 100644 --- a/pyo3cls/src/py_class.rs +++ b/pyo3cls/src/py_class.rs @@ -158,6 +158,19 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident, } }; + // insert space for weak ref + let mut has_weakref = false; + for f in flags.iter() { + if *f == syn::Ident::from("_pyo3::typeob::PY_TYPE_FLAG_WEAKREF") { + has_weakref = true; + } + } + let weakref = if has_weakref { + syn::Ident::from("std::mem::size_of::<*const _pyo3::ffi::PyObject>()") + } else { + syn::Ident::from("0") + }; + quote! { impl _pyo3::typeob::PyTypeInfo for #cls { type Type = #cls; @@ -167,7 +180,8 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident, const SIZE: usize = Self::OFFSET as usize + std::mem::size_of::<#cls>(); const OFFSET: isize = { // round base_size up to next multiple of align - ((<#base as _pyo3::typeob::PyTypeInfo>::SIZE + std::mem::align_of::<#cls>() - 1) / + ((<#base as _pyo3::typeob::PyTypeInfo>::SIZE + #weakref + + std::mem::align_of::<#cls>() - 1) / std::mem::align_of::<#cls>() * std::mem::align_of::<#cls>()) as isize }; diff --git a/src/typeob.rs b/src/typeob.rs index 4d24a2d3..3d99434a 100644 --- a/src/typeob.rs +++ b/src/typeob.rs @@ -170,6 +170,12 @@ pub fn initialize_type<'p, T>(py: Python<'p>, // type size type_object.tp_basicsize = ::SIZE as ffi::Py_ssize_t; + // weakref support (check py3cls::py_class::impl_class) + if T::FLAGS & PY_TYPE_FLAG_WEAKREF != 0 { + type_object.tp_weaklistoffset = T::OFFSET - + (std::mem::size_of::<*const ffi::PyObject>() as isize); + } + // GC support ::update_type_object(type_object); diff --git a/tests/test_class.rs b/tests/test_class.rs index 6b18efe6..1244c252 100644 --- a/tests/test_class.rs +++ b/tests/test_class.rs @@ -488,6 +488,18 @@ fn gc_integration2() { py_run!(py, inst, "import gc; assert inst in gc.get_objects()"); } +#[py::class(weakref)] +struct WeakRefSupport { + token: PyToken, +} +#[test] +fn weakref_support() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let inst = Py::new_ref(py, |t| WeakRefSupport{token: t}).unwrap(); + py_run!(py, inst, "import weakref; assert weakref.ref(inst)() is inst"); +} + #[py::class] pub struct Len { l: usize,