0

I'm trying to create a client using Rust and its Tokio runtime, and bind it to python using pyo3 and pyo3-asyncio. To bring this into a minimal example, let's say I want to have a Client class that connects to 127.0.0.1:1234 and has an echo method that processes data incoming over the socket. I want the function to return a Python coroutine so I can use it alongside async functions in my Python codebase. Example usecase:

import asyncio
from client import Client  # pyo3-bound rust code

c = Client()
asyncio.run(c.echo())

I have followed the docs of pyo3-asyncio and found this function. I wrote this (hopefully minimal) snippet to reproduce the issue:

use pyo3::prelude::*;

use tokio::runtime::Runtime;
use tokio::net::TcpStream;
use tokio::io::AsyncReadExt;


#[pyclass]
struct Client {
    rt: Runtime,
    stream: TcpStream,
}

#[pymethods]
impl Client {
    #[new]
    fn new() -> PyResult<Self> {
        let rt = Runtime::new().expect("Error creating runtime");
        let stream = rt.block_on(async {
            TcpStream::connect("127.0.0.1:1234").await
        })?;
        Ok(Self { rt, stream })
    }

    fn echo(&mut self) -> PyResult<PyObject> {
        let gil = Python::acquire_gil();
        let py = gil.python();
        pyo3_asyncio::tokio::into_coroutine(py, async move {
            loop {
                let mut vec = Vec::new();
                self.stream.read(&mut vec);
                println!("{:?}", vec);
            }
        })
    }
}


#[pymodule]
fn client(py: Python, m: &PyModule) -> PyResult<()> {
    let runtime = Runtime::new().expect("Tokio runtime error");
    m.add_class::<Client>()?;
    pyo3_asyncio::tokio::init(runtime);

    Ok(())
}

The problem seems to be that self, that is Client, has an anonymous lifetime but it is expected to have a 'static one. The compiler says:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/lib.rs:28:60
   |
25 |       fn call_every_second(&mut self, callback: PyObject) -> PyResult<PyObject> {
   |                            --------- this data with an anonymous lifetime `'_`...
...
28 |           pyo3_asyncio::tokio::into_coroutine(py, async move {
   |  ____________________________________________________________^
29 | |             loop {
30 | |                 let mut vec = Vec::new();
31 | |                 self.stream.read(&mut vec);
32 | |                 println!("{:?}", vec);
33 | |             }
34 | |         })
   | |_________^ ...is captured here...
   |

Is there a way I can ensure a static lifetime of the client? Perhaps I'm approaching the problem incorrectly?

E_net4
  • 27,810
  • 13
  • 101
  • 139
Jytug
  • 1,072
  • 2
  • 11
  • 29

1 Answers1

1

The into_coroutine() function requires a 'static future, so you can't borrow Client's stream field inside the future. A workaround is to change Client so it stores stream as Arc<Mutex<TcpStream>>:

struct Client {
    rt: Runtime,
    stream: Arc<Mutex<TcpStream>>, // using tokio::sync::Mutex
}

You can then give into_coroutine() access to the stream through the Arc:

    // ...
    let stream = Arc::clone(&self.stream);
    pyo3_asyncio::tokio::into_coroutine(py, async move {
        loop {
            let mut vec = Vec::new();
            stream.lock().await.read(&mut vec).await?;
            println!("{:?}", vec);
        }
    })
}
Brian Bowman
  • 892
  • 6
  • 9