Передача списка строк из Python в Rust

Я изучаю Rust примерно две недели и сегодня, я попал в его FFI. Я использовал Python для игры с Rust, используя ctypes и libc. Я передал целые числа, строки и даже научился передавать список целых чисел (благодаря этому замечательному ответу).

Затем я попытался передать список строк (следуя аргументам, лежащим в основе ответа), но я потерпел неудачу, так как я не мог понять, как это сделать. В Python у меня есть что-то вроде этого, чтобы передать массив строк.

def testRust():
    lib = ctypes.cdll.LoadLibrary(rustLib)
    list_to_send = ['blah', 'blah', 'blah', 'blah']
    c_array = (ctypes.c_char_p * len(list_to_send))()
    lib.get_strings(c_array, len(list_to_send))

В Rust я думал, что для сбора входящих строк должно быть что-то (например, STRING_RECEIVER), но я не могу найти его.

#![feature(libc)]
extern crate libc;

use std::slice;
use libc::{size_t, STRING_RECEIVER};

#[no_mangle]
pub extern fn get_strings(array: *const STRING_RECEIVER, length: size_t) {
    let values = unsafe { slice::from_raw_parts(array, length as usize) };
    println!("{:?}", values);
}

Есть ли альтернативный способ достижения этого?

Ответ 1

Нет абсолютно никакой разницы в случае массива чисел. Строки C представляют собой массивы с нулевым завершением байтов, поэтому их представление в Rust будет *const c_char, которое затем может быть преобразовано в &CStr, которое затем может быть использовано для получения &[u8], а затем &str.

Python:

import ctypes

rustLib = "libtest.dylib"

def testRust():
    lib = ctypes.cdll.LoadLibrary(rustLib)
    list_to_send = ['blah', 'blah', 'blah', 'blah']
    c_array = (ctypes.c_char_p * len(list_to_send))(*list_to_send)
    lib.get_strings(c_array, len(list_to_send))

if __name__=="__main__":
    testRust()

Ржавчина:

#![feature(libc)]
extern crate libc;

use std::slice;
use std::ffi::CStr;
use std::str;
use libc::{size_t, c_char};

#[no_mangle]
pub extern fn get_strings(array: *const *const c_char, length: size_t) {
    let values = unsafe { slice::from_raw_parts(array, length as usize) };
    let strs: Vec<&str> = values.iter()
        .map(|&p| unsafe { CStr::from_ptr(p) })  // iterator of &CStr
        .map(|cs| cs.to_bytes())                 // iterator of &[u8]
        .map(|bs| str::from_utf8(bs).unwrap())   // iterator of &str
        .collect();
    println!("{:?}", strs);
}

Продолжительность:

% rustc --crate-type=dylib test.rs
% python test.py
["blah", "blah", "blah", "blah"]

И снова вы должны быть осторожны со временем жизни и убедитесь, что Vec<&str> не переживает исходное значение на стороне Python.