Возврат строки из функции Rust в Python

Я очень новичок в Rust. Как я могу вернуть String из функции Rust, которая может использоваться в Python?

Вот моя реализация Rust:

use std::ffi::CString;

#[no_mangle]
pub extern fn query() -> CString {
    let s = CString::new("Hello!").unwrap();
    return s;
}

И код Python, который его вызывает:

from ctypes import cdll, c_char_p

lib = cdll.LoadLibrary("target/release/libtest.so")
result = lib.query()

print(c_char_p(result).value)

Я получаю ошибку сегментации при ее запуске.

EDIT: Используя код Владимира Матвеева Rust ниже, я смог заставить его работать с изменениями в моем коде на Python:

from ctypes import *

lib = cdll.LoadLibrary("target/release/libtest.so")
lib.query.restype = c_char_p
result = lib.query()
print cast(result, c_char_p).value
lib.free_query(result)

Ответы

Ответ 1

Самая прямая версия будет следующей:

use libc::c_char;
use std::ffi::CString;
use std::mem;

#[no_mangle]
pub extern fn query() -> *mut c_char {
    let s = CString::new("Hello!").unwrap();
    s.into_raw()
}

Здесь мы возвращаем указатель на нулевую последовательность char, которая может быть передана Python c_char_p. Вы не можете вернуть только CString, потому что это структура Rust, которая не должна использоваться в C-коде напрямую - она ​​обертывает Vec<u8> и фактически состоит из трех целых чисел с указателем. Он несовместим с C char* напрямую. Нам нужно получить из него необработанный указатель. CString::into_raw() метод делает это: он потребляет значение CString по значению, "забывает" его, поэтому его распределение не будет уничтожено и возвращается a *mut c_char указатель на начало массива.

Однако таким образом строка будет просочиться, потому что мы забываем ее выделение на стороне Rust, и она никогда не будет освобождена. Я не знаю Python FFI достаточно, но самый прямой способ исправить эту проблему - создать две функции: одну для создания данных и одну для ее освобождения. Затем вам нужно освободить данные со стороны Python, вызвав эту функцию освобождения:

// above function
#[no_mangle]
pub extern fn query() -> *mut c_char { ... }

#[no_mangle]
pub extern fn free_query(c: *mut c_char) {
    // convert the pointer back to `CString`
    // it will be automatically dropped immediately
    unsafe { CString::from_raw(c); }
}

CString::from_raw() метод принимает указатель *mut c_char и создает из него экземпляр CString, вычисляя длину нулевого нуля -терминированная строка в этом процессе. Эта операция подразумевает передачу прав собственности, поэтому полученное значение CString будет принадлежать распределению, а когда оно будет сброшено, распределение будет освобождено. Это именно то, что мы хотим.

Ответ 2

Проблема заключается в том, что вы возвращаете CString напрямую, что не соответствует представлению строки в C (вы можете видеть здесь исходный код CString).

Вы должны вернуть указатель на строку, используя s.as_ptr(). Тем не менее, вам нужно убедиться, что строка не освобождена в конце функции, так как это приведет к зависанию указателя.

Единственное решение, о котором я могу думать, это использовать forget, чтобы позволить ржавчине забыть переменную, а не освобождать ее. Конечно, вам нужно будет найти способ освободить строку позже, чтобы избежать утечки памяти (см. Ответ Владимира).

С изменениями, которые я упоминал, ваш код Rust должен быть следующим:

use std::ffi::CString;
use std::mem;

#[no_mangle]
pub extern fn query() -> *const i8 {
    let s = CString::new("Hello!").unwrap();
    let ptr = s.as_ptr();
    mem::forget(s);
    return ptr;
}