3

I have the following example:

use pyo3::prelude::*;
use std::collections::{HashMap, HashSet};
use std::sync::RwLock;

#[pyclass]
struct Rustex {
    map: RwLock<HashMap<String, String>>,
    contexts: RwLock<HashSet<String>>,
}

#[pymethods]
impl Rustex {
    #[new]
    fn new() -> Self {
        Rustex {
            map: RwLock::new(HashMap::new()),
            contexts: RwLock::new(HashSet::new()),
        }
    }

    fn acquire_mutex<'a>(
        &mut self,
        py: Python<'a>,
        mutex_name: String,
        context: String,
    ) -> PyResult<&'a PyAny> {
        pyo3_asyncio::async_std::future_into_py(py, async move {
            let mut map = self.map.write().unwrap();
            Ok(Python::with_gil(|py| py.None()))
        })
    }
}

#[pymodule]
fn rustex(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<Rustex>()?;

    Ok(())
}

When compiled, the error says:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
   --> src/main.rs:27:64
    |
22  |           &mut self,
    |           --------- this data with an anonymous lifetime `'_`...
...
27  |           pyo3_asyncio::async_std::future_into_py(py, async move {
    |  ________________________________________________________________^
28  | |             let mut map = self.map.write().unwrap();
29  | |             Ok(Python::with_gil(|py| py.None()))
30  | |         })
    | |_________^ ...is used here...
    |
note: ...and is required to live as long as `'static` here
   --> src/main.rs:27:9
    |
27  |         pyo3_asyncio::async_std::future_into_py(py, async move {
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: `'static` lifetime requirement introduced by this bound
   --> /Users/shep/.cargo/registry/src/github.com-1ecc6299db9ec823/pyo3-asyncio-0.16.0/src/async_std.rs:318:46
    |
318 |     F: Future<Output = PyResult<T>> + Send + 'static,
    |                                              ^^^^^^^

If I make self 'static then gil isn't happy:

error[E0597]: `_ref` does not live long enough
  --> src/main.rs:11:1
   |
11 | #[pymethods]
   | ^^^^^^^^^^^-
   | |          |
   | |          `_ref` dropped here while still borrowed
   | borrowed value does not live long enough
   | argument requires that `_ref` is borrowed for `'static`
   |
   = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0597]: `gil` does not live long enough
  --> src/main.rs:11:1
   |
11 | #[pymethods]
   | ^^^^^^^^^^^-
   | |          |
   | |          `gil` dropped here while still borrowed
   | borrowed value does not live long enough
   | argument requires that `gil` is borrowed for `'static`
   |
   = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)

Is there a way to either

  • make the self reference not 'static but still available in the async
  • make gil live long enough

I am using the following crates:

pyo3 = { version = "0.16.5", features = ["extension-module"] }
pyo3-asyncio = { version = "0.16", features = ["attributes", "async-std-runtime"] }
async-std = "1.9"
Alex
  • 439
  • 5
  • 16
  • TL;DR you don't. [`future_into_py`](https://docs.rs/pyo3-asyncio/latest/pyo3_asyncio/tokio/fn.future_into_py.html) requires that the future be bound by `'static`. This is basically the same as [How to deal with tokio::spawn closure required to be 'static and &self?](https://stackoverflow.com/q/69955340/155423); [Spawn non-static future with Tokio](https://stackoverflow.com/q/65269738/155423) – Shepmaster Aug 06 '22 at 16:22
  • [The duplicates applied to your situation](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3452c14ebf4a4beb32447f00c7bcd880) – Shepmaster Aug 06 '22 at 16:29
  • I see, this https://stackoverflow.com/questions/69955340/how-to-deal-with-tokiospawn-closure-required-to-be-static-and-self is somewhat similar, it does work with the code you provided, do you want to make it as an answer? I would rename the question then into "Async function with self-reference in Pyo3" – Alex Aug 06 '22 at 21:06
  • @Alex you can answer your own question in this case. – Aitch Aug 06 '22 at 22:13
  • Hope, I explained it correctly, lmk if there is anything more I can add – Alex Aug 06 '22 at 22:47

1 Answers1

3

As correctly pointed out by @ShepMaster, the solution here is to Arc the self-reference in order to allow for multiple ownership. Therefore Rustex is now treated as a 'core'-object and any references go via the Arc. Therefore the structure becomes:

#[derive(Default)]
struct RustexCore {
    map: RwLock<HashMap<String, String>>,
    contexts: RwLock<HashSet<String>>,
}

#[pyclass]
#[derive(Default)]
struct Rustex(Arc<RustexCore>);

Now any function will access one of the references and get access to the object by cloning the reference:

#[pymethods]
impl Rustex {

    fn acquire_mutex<'a>(
        &self,
        py: Python<'a>,
        mutex_name: String,
        context: String
    ) -> PyResult<&'a PyAny> {
        let core = Arc::clone(&self.0);

From there on this core can be used also in the async function. The full code is:

use pyo3::prelude::*;
use std::{
    collections::{HashMap, HashSet},
    sync::{Arc, RwLock},
};

#[derive(Default)]
struct RustexCore {
    map: RwLock<HashMap<String, String>>,
    contexts: RwLock<HashSet<String>>,
}

#[pyclass]
#[derive(Default)]
struct Rustex(Arc<RustexCore>);

#[pymethods]
impl Rustex {
    #[new]
    fn new() -> Self {
        Self::default()
    }

    fn acquire_mutex<'a>(
        &self,
        py: Python<'a>,
        mutex_name: String,
        context: String,
    ) -> PyResult<&'a PyAny> {
        let core = Arc::clone(&self.0);
        pyo3_asyncio::async_std::future_into_py(py, async move {
            let mut map = core.map.write().unwrap();
            Ok(Python::with_gil(|py| py.None()))
        })
    }
}

#[pymodule]
fn rustex(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<Rustex>()?;

    Ok(())
}

Alex
  • 439
  • 5
  • 16