将 Python 列表传递给嵌入式 Rust 函数
Pass Python list to embedded Rust function
我正在学习如何在 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
都是 动态数组 ,但显然我在这里遗漏了一些东西...
为什么我的尝试不起作用?我应该怎么做才能解决它?
ctypes
是关于 C 绑定的,在 C 中没有动态数组这样的东西。
您可以传递给 C 函数的最接近的对象是指向整数的指针,但它不是动态数组,因为
- 不带尺码信息
- 您不能扩大或缩小区域,只能访问现有元素
您可以使用基于函数的 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)
不要这样做:
#[no_mangle]
pub extern fn my_func(my_vec: Vec<i32>) -> i32 { ... }
你基本上永远不会想要在extern
函数中接受或return一个任意的Rust对象,只有Repr
. Instead, you should accept something that is representable by C. As ,对于这种特殊情况,最好的办法是接受一个指针和一个长度。
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,但这个拼凑的代码(在 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))
当然,您可能希望包装它以使其对代码的调用者更好。
我正在学习如何在 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
都是 动态数组 ,但显然我在这里遗漏了一些东西...
为什么我的尝试不起作用?我应该怎么做才能解决它?
ctypes
是关于 C 绑定的,在 C 中没有动态数组这样的东西。
您可以传递给 C 函数的最接近的对象是指向整数的指针,但它不是动态数组,因为
- 不带尺码信息
- 您不能扩大或缩小区域,只能访问现有元素
您可以使用基于函数的 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)
不要这样做:
#[no_mangle]
pub extern fn my_func(my_vec: Vec<i32>) -> i32 { ... }
你基本上永远不会想要在extern
函数中接受或return一个任意的Rust对象,只有Repr
. Instead, you should accept something that is representable by C. As
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,但这个拼凑的代码(在 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))
当然,您可能希望包装它以使其对代码的调用者更好。