I am writing a Rust extension for Python using PyO3 and I am planning to finalise my release for v1.0 which introduces the following async API.
// function signature of `future_into_py` as reference
// 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>
fn hash_async<'a>(&self, py: Python<'a>, bytes: &'a [u8]) -> PyResult<Bound<'a, PyAny>> {
let seed = self.seed;
let hasher = self.hasher;
let bytes_static = unsafe { std::mem::transmute::<&'a [u8], &'static [u8]>(bytes) };
future_into_py(py, async move { gxhash(hasher, bytes_static, seed) })
}
It uses std::mem::transmute to transmute a bytes array, owned by the Python interpreter, into a static lifetime so that it can be used in future_into_py which is essentially a wrapper for tokio::spawn. I understand that doing this is extremely dangerous but my rationale here is that bytes has the same lifetime as py, which is the Python interpreter. As a Rust extension for Python, should I even care what Tokio is doing with my bytes array if the Python interpreter no longer exists?
AFAIK, the only other way to make this work is to clone bytes which I am not willing to do because it is an order of magnitude slower than just passing the bytes array directly.
My question is:
- What are the possible implications for my Python users, if at all?
- Is there a better way to do this without affecting performance?