从 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
值将拥有分配,当它被删除时,分配将被释放。这正是我们想要的。
我对 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
值将拥有分配,当它被删除时,分配将被释放。这正是我们想要的。