2015-05-18 42 views
10

Uczę się, jak osadzać funkcje Rdzy w Pythonie, a wszystko działa dobrze, jeśli moje dane wejściowe są int s, ale nie na liście.Przekaż listę Pythona do wbudowanej funkcji rdzy

Jeśli mój plik lib.rs jest:

#[no_mangle] 
pub extern fn my_func(x: i32, y: i32) -> i32 { 
    return x + y; 
} 

mogę korzystać z tego, co następuje:

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 

Jednak jeśli zmienię lib.rs do:

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

już nie mogę użyj go w Pythonie (ten skompilowany dobrze):

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 

Powodem I choć może to działa jest to, że Pythona list i Rust Vec są oba dynamiczne tablice, ale widocznie jestem czegoś brakuje tutaj ...

Dlaczego moja próba nie działa? Co powinienem zrobić, żeby to naprawić?

Odpowiedz

13

don” t wykonaj następujące czynności:

#[no_mangle] 
pub extern fn my_func(my_vec: Vec<i32>) -> i32 { ... } 

W zasadzie nigdy chcesz zaakceptować lub zwrócić dowolny obiekt Rust w funkcji extern, tylko te, które są Repr. Zamiast tego należy zaakceptować coś, co jest reprezentowalne przez C. Jako 6502 says, najlepszym pomysłem na ten konkretny przypadek byłoby zaakceptowanie wskaźnika i długości.

Rdza Vec jest koncepcyjnie wskaźnikiem do danych, liczbą, i pojemnością. Możesz modyfikować Vec, dodając lub usuwając obiekty, co może spowodować realokację.Jest to podwójnie złe, ponieważ jest prawdopodobne, że Python i Rust używają różnych alokatorów, które nie są ze sobą kompatybilne. Segfaulty leżą w ten sposób! Naprawdę chcesz mieć plaster .

Zamiast zrobić coś takiego na stronie 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) 
} 

Mianowicie używasz typów C zagwarantowane wobec meczu, a następnie przekształcenie wskaźnik i długości do czegoś Rust wie jak sobie radzić z.

nie jestem Pythonista, ale ten kod brukowane-razem (z pomocą How do I convert a Python list into a C array by using ctypes?) wydaje się działać z Rust mam powyżej:

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

Oczywiście, prawdopodobnie chcesz, by zakończyć to zrobić ładniej dla dzwoniącego twojego kodu.

5

ctypes dotyczy więzów C, aw C nie ma czegoś takiego jak tablica dynamiczna.

Najbliższy obiekt można przekazać do funkcji C jest wskaźnikiem do liczby całkowitej, która jednak nie jest dynamiczna tablica ponieważ

  1. To nie niosą informacji o rozmiarze
  2. Nie mogą rosnąć lub kurczyć obszar, wystarczy uzyskać dostęp do istniejących elementów

Prostą alternatywą dla przekazywania wskaźników (i bycia bardzo ostrożnym w unikaniu przekroczenia rozmiaru) może być zamiast tego funkcja API oparta na funkcjach.

Na przykład:

getNumberOfThings() -> number 
getThing(index) -> thing 

ale kod Pythona będzie się wówczas jak

def func(): 
    n = getNumberOfThings() 
    return [getThing(i) for i in range(n)] 

Odpowiednikiem (przejście różną liczbę elementów) byłby

def func2(L): 
    setNumberOfThings(len(L)) 
    for i, x in enumerate(L): 
     setThing(i, x)