将Python列表传递给嵌入式Rust函数

时间:2015-05-18 21:11:07

标签: python rust ctypes

我正在学习如何在Python中嵌入Rust函数,如果我的输入是int s而不是list,那么一切正常。

如果我的lib.rs文件是:

#[no_mangle]
pub extern fn my_func(x: i32, y: i32) -> i32 {
    return x + y;
}

我可以按如下方式使用:

In [1]: from ctypes import cdll

In [2]: lib = cdll.LoadLibrary("/home/user/RustStuff/embed/target/release/libembed.so")

In [3]: lib.my_func(5,6)
Out[3]: 11

但是,如果我将lib.rs更改为:

#[no_mangle]
pub extern fn my_func(my_vec: Vec<i32>) -> i32 {
    let mut my_sum = 0;
    for i in my_vec {
        my_sum += i;
    }
    return my_sum;
}

我不能再在Python中使用它了(编译得很好):

In [1]: from ctypes import cdll

In [2]: lib = cdll.LoadLibrary("/home/user/RustStuff/embed/target/release/libembed.so")

In [3]: lib.my_func([2,3,4])
---------------------------------------------------------------------------
ArgumentError                             Traceback (most recent call last)
<ipython-input-3-454ffc5ba9dd> in <module>()
----> 1 lib.my_func([2,3,4])

ArgumentError: argument 1: <type 'exceptions.TypeError'>: Don't know how to convert parameter 1

原因是,虽然这可行但是Python list和Rust&#39;} Vec都是动态数组,但显然我我在这里错过了什么...

为什么我的尝试不起作用?我该怎么做才能解决它?

2 个答案:

答案 0 :(得分:14)

不要这样做:

#[no_mangle]
pub extern fn my_func(my_vec: Vec<i32>) -> i32 { ... }

你基本上从不想要在extern函数中接受或返回任意Rust对象,只有Repr。相反,你应该接受C代表的东西。作为6502 says,这个特殊情况的最好的想法是接受指针和长度。

Rust的Vec在概念上是指向数据,计数,和容量的指针。您可以通过添加或删除对象来修改Vec,这可能会导致重新分配。这是非常糟糕的,因为Python和Rust可能使用彼此不兼容的不同分配器。 Segfaults就是这样!你真的想要一个切片

相反,在Rust方面做这样的事情:

extern crate libc;

use libc::{size_t,int32_t};
use std::slice;

#[no_mangle]
pub extern fn my_func(data: *const int32_t, length: size_t) -> int32_t {
    let nums = unsafe { slice::from_raw_parts(data, length as usize) };
    nums.iter().fold(0, |acc, i| acc + i)
}

即,您正在使用保证匹配的C类型,然后将指针和长度转换为Rust知道如何处理的内容。

我不是Pythonista,但这个拼凑在一起的代码(在How do I convert a Python list into a C array by using ctypes?的帮助下)似乎与我上面的Rust有关:

import ctypes

lib = ctypes.cdll.LoadLibrary("./target/debug/libpython.dylib")
lib.my_func.argtypes = (ctypes.POINTER(ctypes.c_int32), ctypes.c_size_t)

list_to_sum = [1,2,3,4]
c_array = (ctypes.c_int32 * len(list_to_sum))(*list_to_sum)
print lib.my_func(c_array, len(list_to_sum))

当然,您可能希望将其包装起来,以使代码的调用者更好。

答案 1 :(得分:6)

ctypes是关于C绑定的,而在C中,没有动态数组这样的东西。

您可以传递给C函数的最近对象是指向整数的指针,但不是动态数组,因为

  1. 它不包含尺寸信息
  2. 您无法扩大或缩小区域,只需访问现有元素
  3. 传递指针的一个简单替代方法(并且非常小心不超过大小)可以使用基于函数的API。

    例如:

    getNumberOfThings() -> number
    getThing(index) -> thing
    

    但Python代码将变得像

    def func():
        n = getNumberOfThings()
        return [getThing(i) for i in range(n)]
    

    对应物(传递可变数量的元素)将是

    def func2(L):
        setNumberOfThings(len(L))
        for i, x in enumerate(L):
            setThing(i, x)