pyclass: better error and explanation why lifetimes are disallowed (#2633)
* pyclass: better error and explanation why lifetimes are disallowed * extend detail on lifetimes
This commit is contained in:
parent
0e5e39e63d
commit
c5ba1f0632
|
@ -50,10 +50,28 @@ enum MyEnum {
|
|||
}
|
||||
```
|
||||
|
||||
Because Python objects are freely shared between threads by the Python interpreter, all types annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)).
|
||||
|
||||
The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter.
|
||||
|
||||
### Restrictions
|
||||
|
||||
To integrate Rust types with Python, PyO3 needs to place some restrictions on the types which can be annotated with `#[pyclass]`. In particular, they must have no lifetime parameters, no generic parameters, and must implement `Send`. The reason for each of these is explained below.
|
||||
|
||||
#### No lifetime parameters
|
||||
|
||||
Rust lifetimes are used by the Rust compiler to reason about a program's memory safety. They are a compile-time only concept; there is no way to access Rust lifetimes at runtime from a dynamic language like Python.
|
||||
|
||||
As soon as Rust data is exposed to Python, there is no guarantee which the Rust compiler can make on how long the data will live. Python is a reference-counted language and those references can be held for an arbitrarily long time which is untraceable by the Rust compiler. The only possible way to express this correctly is to require that any `#[pyclass]` does not borrow data for any lifetime shorter than the `'static` lifetime, i.e. the `#[pyclass]` cannot have any lifetime parameters.
|
||||
|
||||
When you need to share ownership of data between Python and Rust, instead of using borrowed references with lifetimes consider using reference-counted smart pointers such as [`Arc`] or [`Py`].
|
||||
|
||||
#### No generic parameters
|
||||
|
||||
A Rust `struct Foo<T>` with a generic parameter `T` generates new compiled implementations each time it is used with a different concrete type for `T`. These new implementations are generated by the compiler at each usage site. This is incompatible with wrapping `Foo` in Python, where there needs to be a single compiled implementation of `Foo` which is integrated with the Python interpreter.
|
||||
|
||||
#### Must be send
|
||||
|
||||
Because Python objects are freely shared between threads by the Python interpreter, there is no guarantee which thread will eventually drop the object. Therefore all types annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)).
|
||||
|
||||
## Constructor
|
||||
|
||||
By default it is not possible to create an instance of a custom class from Python code.
|
||||
|
@ -996,12 +1014,14 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
|||
[`GILGuard`]: {{#PYO3_DOCS_URL}}/pyo3/struct.GILGuard.html
|
||||
[`PyTypeInfo`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeInfo.html
|
||||
|
||||
[`Py`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Py.html
|
||||
[`PyCell`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html
|
||||
[`PyClass`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/trait.PyClass.html
|
||||
[`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html
|
||||
[`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html
|
||||
[`PyClassInitializer<T>`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass_init/struct.PyClassInitializer.html
|
||||
|
||||
[`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
|
||||
[`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
|
||||
|
||||
[classattr]: https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables
|
||||
|
|
|
@ -197,9 +197,19 @@ pub fn build_py_class(
|
|||
);
|
||||
let krate = get_pyo3_crate(&args.options.krate);
|
||||
|
||||
if let Some(lt) = class.generics.lifetimes().next() {
|
||||
bail_spanned!(
|
||||
lt.span() =>
|
||||
"#[pyclass] cannot have lifetime parameters. \
|
||||
For an explanation, see https://pyo3.rs/latest/class.html#no-lifetime-parameters"
|
||||
);
|
||||
}
|
||||
|
||||
ensure_spanned!(
|
||||
class.generics.params.is_empty(),
|
||||
class.generics.span() => "#[pyclass] cannot have generic parameters"
|
||||
class.generics.span() =>
|
||||
"#[pyclass] cannot have generic parameters. \
|
||||
For an explanation, see https://pyo3.rs/latest/class.html#no-generic-parameters"
|
||||
);
|
||||
|
||||
let field_options = match &mut class.fields {
|
||||
|
|
|
@ -5,4 +5,9 @@ struct ClassWithGenerics<A> {
|
|||
a: A,
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
struct ClassWithLifetimes<'a> {
|
||||
a: &'a str,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
error: #[pyclass] cannot have generic parameters
|
||||
error: #[pyclass] cannot have generic parameters. For an explanation, see https://pyo3.rs/latest/class.html#no-generic-parameters
|
||||
--> tests/ui/reject_generics.rs:4:25
|
||||
|
|
||||
4 | struct ClassWithGenerics<A> {
|
||||
| ^
|
||||
|
||||
error: #[pyclass] cannot have lifetime parameters. For an explanation, see https://pyo3.rs/latest/class.html#no-lifetime-parameters
|
||||
--> tests/ui/reject_generics.rs:9:27
|
||||
|
|
||||
9 | struct ClassWithLifetimes<'a> {
|
||||
| ^^
|
||||
|
|
Loading…
Reference in New Issue