从 Rust 函数返回一个字符串到 Python

Returning a String from Rust function to Python

我对 Rust 很陌生。我如何 return 来自可用于 Python 的 Rust 函数的 String

这是我的 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)

我在 运行 时遇到分段错误。

编辑:使用下面的 Vladimir Matveev 的 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)

这里的问题是你直接返回的是一个CString,与C中字符串的表示不对应(可以看here的源码CString).

您应该使用 s.as_ptr() 返回指向字符串的指针。但是,您需要确保在函数末尾未释放字符串,因为这会导致悬空指针。

我能想到的唯一解决方案是使用forget让rust忘记变量而不是释放它。当然,您以后需要找到一种方法来释放字符串以避免内存泄漏(请参阅 Vladimir 的回答)。

根据我提到的更改,您的 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;
}

最直接的版本是这样的:

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

这里我们 return 一个指向 char 的零终止序列的指针,它可以传递给 Python 的 c_char_p。你不能 return 只是 CString 因为它是 Rust 结构,不应该直接在 C 代码中使用 - 它包装 Vec<u8> 并且实际上由三个指针大小的整数组成。它不直接与 C 的 char* 兼容。我们需要从中获取原始指针。 CString::into_raw() 方法执行此操作 - 它按值消耗 CString,"forgets" 它因此它的分配不会被破坏,并且 return 是一个 *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 值将拥有分配,当它被删除时,分配将被释放。这正是我们想要的。