In the current architecture, to support specific Rust runtimes, each runtime interface requires explicitly defining a free function in its respective module that calls the identically named function in the generic module, passing the corresponding runtime generic parameter R.
// src/async_std.rs
pub fn future_into_py<F, T>(py: Python, fut: F) -> PyResult<Bound<PyAny>>
where
F: Future<Output = PyResult<T>> + Send + 'static,
T: for<'py> IntoPyObject<'py> + Send + 'static,
{
generic::future_into_py::<AsyncStdRuntime, _, T>(py, fut)
}
// src/generic.rs
pub fn future_into_py<R, F, T>(py: Python, fut: F) -> PyResult<Bound<PyAny>>
where
R: Runtime + ContextExt,
F: Future<Output = PyResult<T>> + Send + 'static,
T: for<'py> IntoPyObject<'py> + Send + 'static,
{
future_into_py_with_locals::<R, F, T>(py, get_current_locals::<R>(py)?, fut)
}
This design results in significant code duplication across runtime modules, differing only in the runtime generic parameter R passed. Additionally, supporting new runtimes requires copying this code. To eliminate this duplication, I propose using default trait implementations to encapsulate the free functions within the generic module.
pub trait HybridRuntimeMethods<R> {
...
fn future_into_py<F, T>(py: Python, fut: F) -> PyResult<Bound<PyAny>>
where
R: Runtime + ContextExt,
F: Future<Output = PyResult<T>> + Send + 'static,
T: for<'py> IntoPyObject<'py> + Send + 'static,
{
Self::future_into_py_with_locals::<F, T>(py, Self::get_current_locals(py)?, fut)
}
...
}
pub struct HybridRuntime<R>(PhantomData<R>);
impl<R> HybridRuntimeMethods<R> for HybridRuntime<R> {}
By using the blanket implementation impl<R> HybridRuntimeMethods<R> for HybridRuntime<R> {} to provide the default implementation, runtime modules no longer need to define these functions explicitly. They can be used directly as shown below:
use pyo3::{prelude::*, wrap_pyfunction};
use pyo3_async_runtimes::generic::HybridRuntimeMethods;
type HybridRuntime = pyo3_async_runtimes::generic::HybridRuntime<async_std_runtime::AsyncStdRuntime>;
#[pyfunction]
fn rust_sleep(py: Python) -> PyResult<Bound<PyAny>> {
HybridRuntime::future_into_py(py, async {
println!("sleeping for 1s");
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
println!("done");
Ok(())
})
}
#[pymodule]
fn async_std_rs_sleep(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
Ok(())
}
In the current architecture, to support specific Rust runtimes, each runtime interface requires explicitly defining a free function in its respective module that calls the identically named function in the
genericmodule, passing the corresponding runtime generic parameterR.This design results in significant code duplication across runtime modules, differing only in the runtime generic parameter
Rpassed. Additionally, supporting new runtimes requires copying this code. To eliminate this duplication, I propose using default trait implementations to encapsulate the free functions within thegenericmodule.By using the blanket implementation
impl<R> HybridRuntimeMethods<R> for HybridRuntime<R> {}to provide the default implementation, runtime modules no longer need to define these functions explicitly. They can be used directly as shown below: