vacia una tuplas por metodos llenar listas lista elementos diccionarios crear comprensión agregar python rust ctypes

tuplas - llenar una lista en python



Convertir el vector de óxido de tuplas en una estructura compatible con C (1)

Siguiendo these answers , actualmente he definido una función Rust 1.0 de la siguiente manera, para poder llamarme desde Python usando ctypes :

use std::vec; extern crate libc; use libc::{c_int, c_float, size_t}; use std::slice; #[no_mangle] pub extern fn convert_vec(input_lon: *const c_float, lon_size: size_t, input_lat: *const c_float, lat_size: size_t) -> Vec<(i32, i32)> { let input_lon = unsafe { slice::from_raw_parts(input_lon, lon_size as usize) }; let input_lat = unsafe { slice::from_raw_parts(input_lat, lat_size as usize) }; let combined: Vec<(i32, i32)> = input_lon .iter() .zip(input_lat.iter()) .map(|each| convert(*each.0, *each.1)) .collect(); return combined }

Y estoy configurando la parte de Python así:

from ctypes import * class Int32_2(Structure): _fields_ = [("array", c_int32 * 2)] rust_bng_vec = lib.convert_vec_py rust_bng_vec.argtypes = [POINTER(c_float), c_size_t, POINTER(c_float), c_size_t] rust_bng_vec.restype = POINTER(Int32_2)

Esto parece estar bien, pero yo soy:

  • No estoy seguro de cómo transformar combined (un Vec<(i32, i32)> ) en una estructura compatible con C, por lo que se puede devolver a mi script de Python.
  • No estoy seguro de si debería devolver una referencia (¿ return &combined ?) Y cómo tendría que anotar la función con el especificador de vida útil adecuado si lo hiciera

Lo más importante a tener en cuenta es que no hay tal cosa como una tupla en C. C es la lengua franca de la interoperabilidad de la biblioteca, y se le pedirá que se limite a las habilidades de este idioma. No importa si está hablando entre Rust y otro lenguaje de alto nivel; tienes que hablar C.

Puede que no haya tuplas en C, pero hay struct . ¡Una tupla de dos elementos es solo una estructura con dos miembros!

Comencemos con el código C que escribiríamos:

#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; }

Hemos definido dos struct : una para representar nuestra tupla y otra para representar una matriz, ya que las pasaremos de un lado a otro.

Seguiremos esto definiendo exactamente las mismas estructuras en Rust y las definiremos para que tengan exactamente los mismos miembros (tipos, orden, nombres). Es importante destacar que usamos #[repr(C)] para que el compilador Rust sepa que no debe hacer nada extravagante al reordenar los datos.

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) }

Nunca debemos aceptar ni devolver tipos no- repr(C) través del límite FFI, por lo que pasamos a través de nuestro Array . Tenga en cuenta que hay una buena cantidad de código unsafe , ya que tenemos que convertir un puntero desconocido a datos ( c_void ) a un tipo específico. Ese es el precio de ser genérico en el mundo C

Vayamos ahora a Python. Básicamente, solo tenemos que imitar lo que hizo el código 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

Perdona mi rudimentaria pitón. ¡Estoy seguro de que un pitonista experimentado podría hacer que esto se vea mucho más bonito! Gracias a @eryksun por algunos buenos consejos sobre cómo hacer que el lado del consumidor de calificar el método sea mucho más agradable.

Unas palabras sobre propiedad y pérdidas de memoria.

En este código de ejemplo, hemos filtrado la memoria asignada por el Vec . Teóricamente, el código FFI ahora es el propietario de la memoria, pero en realidad no puede hacer nada útil con él. Para tener un ejemplo totalmente correcto, necesitaría agregar otro método que acepte el puntero de la persona que lo llama, lo transforme de nuevo en un Vec y luego permita que Rust elimine el valor. Esta es la única forma segura, ya que Rust está casi garantizado para usar un asignador de memoria diferente al que usa su idioma FFI.

No estoy seguro de si debería devolver una referencia y cómo tendría que anotar la función con el especificador de vida útil adecuado si lo hiciera

No, no desea (leer: no puede ) devolver una referencia. Si pudiera, la propiedad del elemento terminaría con la llamada a la función y la referencia no apuntaría a nada. Es por eso que necesitamos hacer el baile de dos pasos con mem::forget y devolver un puntero en bruto.