Search code examples
pythonrustffi

Pass Python list to Rust function


I have a Rust library that needs to be imported into Python via the ctypes module. My goal is to use Rust functions that take Vec<T> / i32 as arguments and return those types, from Python. Currently, I can pass integers to the Rust functions, and have them return lists / integers. Here is the current code:

Python:

import ctypes
from ctypes import cdll

class List_4(ctypes.Structure):
    _fields_ = [("array", ctypes.ARRAY(ctypes.c_int32, 4))]

rust = cdll.LoadLibrary("target/debug/py_link.dll")
rust.function_vec.restype = List_4

foobar = rust.function_i32(5)
barbaz = rust.function_vec([1, 2, 3, 4]) # Throws error: Don't know how to convert parameter

print foobar
print barbaz

Rust:

#[repr(C)]
pub struct List_4 {
    array: [i32; 4]
}

#[no_mangle]
pub extern fn function_i32(number: i32) -> i32 {
    number
}

#[no_mangle]
pub extern fn function_vec(list: List_4) -> List_4 {
    List_4 { array: [1, 2, 3, 5] }
}

What I need help with is passing a Python list as an argument to a Rust function. My best guess is to pass a ctypes.ARRAY to the function rather than a list, but I am not sure how to go about converting a Python list to that type.

Note: I tried the Rust code from this related question but it says "linking with `gcc` failed: exit code: 1" and "bad reloc address" when I try to compile it.


Solution

  • Looks like I solved the problem. I turned the Python list into a C array, and passed that to the Rust function. Here is the working code:

    #[repr(C)]
    pub struct List_4 {
        // Create a struct using #[repr(C)], will do the same in Python so it is shareable
        array: [i32; 4]
    }
    
    #[no_mangle]
    pub extern fn function_array(list: List_4) -> List_4 {
        // Return a new instance of List_4
        List_4 { array: [1, 2, 3, 5] }
    }
    

    Python:

    import ctypes # By using ctypes, and #[repr(C)], we use the same type
                  # of data in both languages, so it is possible to send stuff between them
    
    rust = cdll.LoadLibrary("target/debug/py_link.dll") # Import the Rust dll
    
    class List_4(ctypes.Structure):
        # Similar to creating the struct in Rust
        _fields_ = [("array", ctypes.ARRAY(ctypes.c_int32, 4))]
    
    rust.function_array.restype = List_4 # Set the function's return type to List_4
    
    def function_array(arr):
        # For convenience, a function to convert a list to a C array, call the function,
        # and convert its return value to a list
        return list(
            rust.function_array(
                (ctypes.c_int * len(lst))(*lst) # Call the function after converting the list 
            ).array
        )
    
    # To use the function:
    >>> function_array([1, 2, 3])
    [1, 2, 3, 5]