Ответ 1
Самое главное отметить, что не такая вещь, как кортеж в C. C является лингва-франкой библиотечной совместимости, и вам придется ограничить себя возможностями этого язык. Неважно, если вы говорите между Rust и другим языком высокого уровня; вы должны говорить C.
В C не может быть кортежей, но есть struct
s. Двухэлементный кортеж - это просто структура с двумя членами!
Начнем с кода C, который мы будем писать:
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint32_t a;
uint32_t b;
} tuple_t;
typedef struct {
void *data;
size_t len;
} array_t;
extern array_t convert_vec(array_t lat, array_t lon);
int main() {
uint32_t lats[3] = {0, 1, 2};
uint32_t lons[3] = {9, 8, 7};
array_t lat = { .data = lats, .len = 3 };
array_t lon = { .data = lons, .len = 3 };
array_t fixed = convert_vec(lat, lon);
tuple_t *real = fixed.data;
for (int i = 0; i < fixed.len; i++) {
printf("%d, %d\n", real[i].a, real[i].b);
}
return 0;
}
Мы определили два struct
- один для представления нашего кортежа, а другой - для представления массива, поскольку мы будем немного передавать их назад и вперед.
Мы рассмотрим это, определив те же самые структуры в Rust и определим их, чтобы они имели одинаковые члены (типы, порядок, имена). Важно отметить, что мы используем #[repr(C)]
, чтобы компилятор Rust знал, что он не делает ничего смешного с переупорядочиванием данных.
extern crate libc;
use std::slice;
use std::mem;
#[repr(C)]
pub struct Tuple {
a: libc::uint32_t,
b: libc::uint32_t,
}
#[repr(C)]
pub struct Array {
data: *const libc::c_void,
len: libc::size_t,
}
impl Array {
unsafe fn as_u32_slice(&self) -> &[u32] {
assert!(!self.data.is_null());
slice::from_raw_parts(self.data as *const u32, self.len as usize)
}
fn from_vec<T>(mut vec: Vec<T>) -> Array {
// Important to make length and capacity match
// A better solution is to track both length and capacity
vec.shrink_to_fit();
let array = Array { data: vec.as_ptr() as *const libc::c_void, len: vec.len() as libc::size_t };
// Whee! Leak the memory, and now the raw pointer (and
// eventually C) is the owner.
mem::forget(vec);
array
}
}
#[no_mangle]
pub extern fn convert_vec(lon: Array, lat: Array) -> Array {
let lon = unsafe { lon.as_u32_slice() };
let lat = unsafe { lat.as_u32_slice() };
let vec =
lat.iter().zip(lon.iter())
.map(|(&lat, &lon)| Tuple { a: lat, b: lon })
.collect();
Array::from_vec(vec)
}
Мы не должны принимать или возвращать типы не repr(C)
по границе FFI, поэтому мы проходим через наш Array
. Обратите внимание, что существует хороший код unsafe
, так как мы должны преобразовать неизвестный указатель на данные (c_void
) в определенный тип. Это цена родового в мире C.
Теперь повернемся к Питону. В принципе, нам просто нужно подражать тому, что сделал код C:
import ctypes
class FFITuple(ctypes.Structure):
_fields_ = [("a", ctypes.c_uint32),
("b", ctypes.c_uint32)]
class FFIArray(ctypes.Structure):
_fields_ = [("data", ctypes.c_void_p),
("len", ctypes.c_size_t)]
# Allow implicit conversions from a sequence of 32-bit unsigned
# integers.
@classmethod
def from_param(cls, seq):
return cls(seq)
# Wrap sequence of values. You can specify another type besides a
# 32-bit unsigned integer.
def __init__(self, seq, data_type = ctypes.c_uint32):
array_type = data_type * len(seq)
raw_seq = array_type(*seq)
self.data = ctypes.cast(raw_seq, ctypes.c_void_p)
self.len = len(seq)
# A conversion function that cleans up the result value to make it
# nicer to consume.
def void_array_to_tuple_list(array, _func, _args):
tuple_array = ctypes.cast(array.data, ctypes.POINTER(FFITuple))
return [tuple_array[i] for i in range(0, array.len)]
lib = ctypes.cdll.LoadLibrary("./target/debug/libtupleffi.dylib")
lib.convert_vec.argtypes = (FFIArray, FFIArray)
lib.convert_vec.restype = FFIArray
lib.convert_vec.errcheck = void_array_to_tuple_list
for tupl in lib.convert_vec([1,2,3], [9,8,7]):
print tupl.a, tupl.b
Простите мой рудиментарный Python. Я уверен, что опытный Pythonista мог бы сделать это намного красивее! Благодаря @eryksun для несколько приятных советов о том, как сделать потребителя сторона вызова метода намного.
Слово об ответственности и утечке памяти
В этом примере кода мы утекли память, выделенную Vec
. Теоретически, код FFI теперь владеет памятью, но реально он ничего не может с этим поделать. Чтобы иметь полностью правильный пример, вам нужно добавить еще один метод, который будет принимать указатель назад от вызываемого абонента, преобразовать его обратно в Vec
, а затем разрешить Rust отказаться от значения. Это единственный безопасный способ, так как Rust почти гарантированно использует другой распределитель памяти, чем тот, который использует ваш язык FFI.
Не уверен, должен ли я возвращать ссылку и как мне пришлось бы аннотировать функцию с соответствующим спецификатором срока службы, если бы я сделал
Нет, вы не хотите (читать: не можете) вернуть ссылку. Если бы вы могли, тогда право собственности на элемент закончилось вызовом функции, и ссылка не указала бы на ничего. Вот почему нам нужно сделать двухэтапный танец с mem::forget
и вернуть необработанный указатель.