Skip to content

Use default trait implementations for runtime interfaces (e.g., future_into_py) instead of free functions #70

@Varixer

Description

@Varixer

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(())
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions