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

11

我正在学习如何在Python中嵌入Rust函数,如果我的输入是int类型,则一切正常,但如果是列表,则不行。

如果我的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的Vec都是动态数组,但显然我在某些方面还存在问题...
为什么我的尝试不起作用?我应该怎么做才能修复它?
2个回答

14
不要这样做:
#[no_mangle]
pub extern fn my_func(my_vec: Vec<i32>) -> i32 { ... }

你基本上永远不想在extern函数中接受或返回任意Rust对象,只想要Repr的对象。相反,你应该接受可以用C表示的东西。正如6502所说,对于这种特殊情况,最好的想法是接受一个指针和长度。
Rust的Vec在概念上是指向数据、计数和容量的指针。你可以通过添加或删除对象来修改Vec,这可能会导致重新分配。这是两倍的坏处,因为Python和Rust可能使用不兼容的不同分配器。段错误就藏在这里!你真的需要一个slice
而是在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,但这个拼凑而成的代码(在如何使用ctypes将Python列表转换为C数组?的帮助下)似乎可以与我上面提到的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))

当然,你可能希望将其包装起来,以使调用者的代码更加优雅。

6

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

你可以传递给C函数的最接近的对象是一个指向整数的指针,但它并不是动态数组,因为:

  1. 它没有携带大小信息
  2. 你不能增加或缩小该区域,只能访问现有元素

一个简单的替代方案是使用基于函数的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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接