从Rust函数返回字符串到Python

24

我对Rust很陌生。如何从Rust函数返回一个可以在Python中使用的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)

请查看 https://dev59.com/O4vda4cB1Zd3GeqPWjy5 和 https://dev59.com/PV0a5IYBdhLWcg3wdIbo#30313295 并告诉我们您的问题有何不同。 - Shepmaster
我已经审查了这两个问题,它们是不同的。在第一个问题中,调用来自Ruby,而我的问题来自Python。在第二个问题中,返回值是一个整数,这是一个简单的情况。在这里,返回值特别是一个字符串值。 - LeeMobile
1
在 Rust 方面,无论从哪种语言进行调用,都不应该有任何不同。就 Rust 代码而言,C 在调用它。对于它来说,其他所有语言都是在调用看起来像 C 代码的东西。 - Shepmaster
2个回答

16

最直接的版本应该是这样的:

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,“忘记”它以使其分配不会被销毁,并返回指向数组开头的*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 值将拥有该分配,当它被丢弃时,该分配就会被释放。这正是我们想要的。


我需要完成这里的前两个步骤才能使其正常工作:(1)在Cargo.toml中添加对rustc的依赖,(2)在使用它的rust文件中导入它。(不确定是否总是必要的,因为我完全是新手。) - ArneHugo

1
这里的问题是您直接返回了一个 CString,这不对应于在 C 中表示字符串的方式(您可以在 此处 查看 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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接