Skip to content

API for returning a memoryview pointing at memory in a Py<T> #5871

@alex

Description

@alex

We've got APIs that give you a memoryview given a Python object that implements the buffer protocol, but not one that gives you a memoryview just given a Py that stores some bytes!

I think it's possible to do this soundly!

As concrete, motivating examples:

These allocates new PyBytes every time they're accessed, doing allocations and copying all those bytes. Sad!

Here's a rough sketch of what I think can work:

impl PyMemoryView {
    pub fn from_owned_buffer<'py, T>(
        py: Python<'py>,
        owner: Py<T>,
        getbuf: impl for<'a> FnOnce(&'a T) -> &'a [u8],
    ) -> PyResult<Bound<'py, Self>>
    where
        T: PyClass<Frozen = True>,
    {
        let data: &T = owner.get();
        let buf: &[u8] = getbuf(data);
        let buf_ptr = buf.as_ptr();
        let buf_len = buf.len();
    
        let obj_ptr = owner.into_ptr();
    
        let mut view: ffi::Py_buffer = unsafe { std::mem::zeroed() };
    
        let rc = unsafe {
            ffi::PyBuffer_FillInfo(
                &mut view,
                obj_ptr,
                buf_ptr as *mut _,
                buf_len as ffi::Py_ssize_t,
                1,                // readonly
                ffi::PyBUF_FULL_RO,
            )
        };
        if rc < 0 {
            unsafe { ffi::Py_DECREF(obj_ptr); }
            return Err(PyErr::fetch(py));
        }
    
        let mv_ptr = unsafe { ffi::PyMemoryView_FromBuffer(&view) };
        if mv_ptr.is_null() {
            // PyMemoryView_FromBuffer calls PyBuffer_Release on failure,
            // which decrefs view.obj — so we do NOT decref again.
            return Err(PyErr::fetch(py));
        }
    
        // PyBuffer_FillInfo already incref'd obj_ptr via view.obj,
        // but we consumed a refcount from into_ptr() that nobody owns
        // now. Decref the extra one.
        unsafe { ffi::Py_DECREF(obj_ptr); }
    
        unsafe {
            Ok(Bound::from_owned_ptr(py, mv_ptr).downcast_into_unchecked())
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions