我正在包装一个C API (dylib(,它为最后一条错误消息公开了一个setter和getter API:
extern "C" {
/// GetLastError is thread-safe
pub fn GetLastError() -> *const ::std::os::raw::c_char;
pub fn SetLastError(msg: *const ::std::os::raw::c_char);
}
包装它们的最简单方法如下
use std::error::Error;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::os::raw::c_char;
pub struct MyError;
impl MyError {
pub fn get_last() -> &'static str {
unsafe {
match CStr::from_ptr(c_api::GetLastError()).to_str() {
Ok(s) => s,
Err(_) => "Invalid UTF-8 message",
}
}
}
pub fn set_last(msg: &'static str) {
unsafe {
c_api::SetLastError(msg.as_ptr() as *const c_char);
}
}
}
impl Display for MyError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", MyError::get_last())
}
}
impl Error for MyError {
fn description(&self) -> &'static str {
MyError::get_last()
}
fn cause(&self) -> Option<&Error> {
None
}
}
这似乎可以正常工作,以获取最后一条错误消息。但是,设置最后一个错误消息似乎很幼稚,并且会使堆栈帧中的先前消息混乱!
例如;
let msg: &'static str = "invalid";
MyError::set_last(msg);
println!("Last error msg: {}", MyError::get_last());
输出Last error msg: invalidLast error msg
或
assert_eq!(MyError::get_last().trim(), msg);
失败与
thread 'tests::set_error' panicked at 'assertion failed: `(left == right)`
left: `"invalidassertion failed: `(left == right)`n left: ``,n right: ``"`,
right: `"invalid"`'
这样做的正确方法是什么?
我想使用回溯,但在板条箱中发现几乎没有解释,它无处可去!
Rust 字符串不是以 0 结尾的,但是,当您这样做时,您假设它们是:
c_api::SetLastError(msg.as_ptr() as *const c_char);
因为 C API 通过是否存在空字节来检测字符串的结尾。
执行此操作的正确方法是:
let c_string = CString::new("message").unwrap(); // will add a null byte
unsafe {
c_api::SetLastError(c_string.as_ptr());
}
根据 C API 是否创建字符串的副本,您可能需要在必要时使用into_raw
以及适当的取消初始化处理。