使用ctypes在Python中使用Rust返回数组

使用ctypes在Python中使用Rust返回数组,python,rust,ctypes,Python,Rust,Ctypes,我有一个Rust函数,它返回一个数组,我想在Python中使用这个数组,它可以是一个列表或numpy。数组其实并不重要 我的函数如下所示: #[no_mangle] pub extern fn make_array() -> [i32; 4] { let my_array: [i32; 4] = [1,2,3,4]; return my_array; } In [20]: import ctypes In [21]: from ctypes import cdll I

我有一个Rust函数,它返回一个
数组
,我想在
Python
中使用这个数组,它可以是一个
列表
numpy。数组
其实并不重要

我的函数如下所示:

#[no_mangle]
pub extern fn make_array() -> [i32; 4] {
    let my_array: [i32; 4] = [1,2,3,4];
    return my_array;
}
In [20]: import ctypes

In [21]: from ctypes import cdll

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

In [23]: lib.make_array.restype = ctypes.ARRAY(ctypes.c_int32, 4)

In [24]: temp = lib.make_array()

In [25]: [i for i in temp]
Out[25]: [1, 2, -760202930, 32611]
我试着用Python这样调用它:

#[no_mangle]
pub extern fn make_array() -> [i32; 4] {
    let my_array: [i32; 4] = [1,2,3,4];
    return my_array;
}
In [20]: import ctypes

In [21]: from ctypes import cdll

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

In [23]: lib.make_array.restype = ctypes.ARRAY(ctypes.c_int32, 4)

In [24]: temp = lib.make_array()

In [25]: [i for i in temp]
Out[25]: [1, 2, -760202930, 32611]
我做错了什么?为什么我的输出不是
[1,2,3,4]
?为什么我的前两个元素是正确的,而另外两个元素填充了垃圾

我找不到任何关于
ctypes.ARRAY
的好文档,所以我只选择了看起来正确的文档,所以这可能就是问题所在。

我同意所说的-。一个主要的不兼容性是Rust数组知道它们的大小,而C数组不知道。您需要遵守其他所有C程序的操作方式—分别返回指针和长度。相比之下,Rust不是一种很好的现代语言吗

我窃取并修改了一些Python代码

导入ctypes
从ctypes导入cdll
lib=cdll.LoadLibrary(“libarray.dylib”)
lib.make_array.restype=ctypes.POINTER(ctypes.c_int32*4)
打印[i for i in lib.make_array().contents]
这适用于以下防锈代码:

静态数组:[i32;4]=[1,2,3,4];
#[没有损坏]
pub extern fn make_array()->*常量i32{
ARRAY.as_ptr()
}

在这里,我们做的是最简单的事情,创建一个数组,该数组将在程序的整个长度内有效,并返回对其数据的引用。在实际的程序中,您可能需要更加小心,以确保
Vec
&[i32]
严格超过Python代码具有指针的时间,否则将导致内存损坏。

正如其他人所说,您无法正确返回固定大小的数组。但是,您可以通过将数组包装到一个结构中,诱使ctypes做正确的事情:

import ctypes

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

lib = ctypes.CDLL("embed.dll")
lib.make_array.restype = Int32_4

temp = lib.make_array()

print(temp.array[:])
这将导致我的机器上出现
[1,2,3,4]

附录:这是一个“把戏”,因为我们正在利用C和Rust之间的差异。C不允许您按值返回固定大小的数组,但Rust会,其工作原理与返回用户定义的结构相同

所以,我们做了一些C允许的事情:返回一个恰好包含固定大小数组的结构。这一点很好,并且与Rust使用的布局相匹配

当然,这也有点骇人听闻,因为我不完全相信这是定义明确的行为。如果您想更加安全,可以将生锈侧的返回类型更改为与C匹配:

#[repr(C)]
结构Int32_4{
数组:[i32;4]
}

一个小样式的nit,但是您的Rust函数可能是
pub extern fn make_array()->[i32;4]{[1,2,3,4]}
@Shepmaster,我明白了,谢谢。我确实需要习惯正确的
rust
风格。我不知道rust的ABI如何处理返回的固定大小数组,但AFAIK C不能这样做,所以我不希望ctypes或任何C ABI使用者能够处理它。根据我的结果,似乎指向第一个元素的指针实际上被传递了,因为前两个元素是对的,所以长度是错的@Akavall我不知道Rust阵列的内存布局是什么,所以很难猜测发生了什么。其中一些数字可能是未初始化的填充字节。数组的大小可能是从
u8
u64
(或者可能是
usize
)的任意大小,因此要解码您正在打印的内存并不容易。很好,但是您能解释一下您的解决方案是如何工作的吗。例如,我不明白为什么这是一个“把戏”,而不是正常使用
ctypes
。现在就完全有道理了。谢谢。@DK:内存中的布局将匹配,因为C标准要求结构的地址与其第一个数据成员的地址匹配(即,无前置填充);它可能会违反严格的别名规则,但跨语言使用它并不重要。尽管如此,最好在两端都使用
struct
,这将减少未来维护人员的头痛。@MatthieuM。当我说我不相信这是一种定义明确的行为时,我的意思是在事物的生锈方面。当然,按值返回数组就像它是一个结构一样是有意义的,但改变它并不会破坏与C代码的集成;C不能按值返回数组,因此肯定没有人依赖于确切的行为!;)