Support returning &mut Self for method chaining
It would be extremely helpful to support easily binding APIs which rely on method chaining (obj.a().b().c() where a, b, and c are instance methods). The builder pattern is one of the more frequent use cases, though it's far from the only one.
In Rust, these methods look like pub fn foo(&mut self, ...) -> &mut Self {...}. Unfortunately such functions can't be used in a #[pymethods] block at the moment
I'm still a little new to rust, but it seems strange to me that this doesn't work since Self since we know already that Self is a PyObject. Unfortunately, I have no real idea how something like this might be implemented, or if there's language reasons why this can't be done.
Example:
#[pyclass]
pub struct SimpleChaining{}
#[pymethods]
impl SimpleChaining{
pub fn a(&mut self) -> &mut Self {
self
}
}
Currently fails with
error[E0277]: the trait bound `&mut SimpleChaining: OkWrap<&mut SimpleChaining>` is not satisfied
--> lib\generators\src\scenario_generator.rs:122:1
|
122 | #[pymethods]
| ^^^^^^^^^^^^ the trait `IntoPy<Py<PyAny>>` is not implemented for `&mut SimpleChaining`, which is required by `&mut SimpleChaining: OkWrap<_>`
|
= help: the trait `IntoPy<Py<PyAny>>` is implemented for `SimpleChaining`
= note: required for `&mut SimpleChaining` to implement `OkWrap<&mut SimpleChaining>`
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
For various reasons this isn't particularly practical -- pyo3 has no way of mapping the &mut reference that's returned back to the original instance. The right way to do this is something like:
fn method(slf: pyo3::PyRef<'_, Self>) -> pyo3::PyRef<'_, Self> {
self
}
Adding on to alex's point, you can instead use PyRefMut for both the argument and the return value to let you mutate the type (since PyRefMut<T> dereferences to &mut T), which is typically what you want for builders.
For example, a basic builder pattern might look something like this:
use pyo3::prelude::*;
use pyo3::exceptions::PyValueError;
#[pyclass]
struct MyProduct {
name: String,
value: i32,
}
#[pyclass]
struct MyBuilder {
name: Option<String>,
value: Option<i32>,
}
#[pymethods]
impl MyBuilder {
#[new]
fn new() -> Self {
Self { name: None, value: None }
}
fn name(mut slf: PyRefMut<Self>, name: String) -> PyRefMut<Self> {
slf.name = Some(name);
slf
}
fn value(mut slf: PyRefMut<Self>, value: i32) -> PyRefMut<Self> {
slf.value = Some(value);
slf
}
fn build(slf: PyRef<Self>) -> PyResult<MyProduct> {
let name = slf.name.as_ref().cloned()
.ok_or_else(|| PyValueError::new_err("name not set"))?;
let value = slf.value.as_ref().cloned()
.ok_or_else(|| PyValueError::new_err("value not set"))?;
Ok(MyProduct { name, value })
}
}
Which you could then use from Python like this:
product = MyBuilder().name("fred").value(10).build()
Closing as resolved.