2

I'm wrapping a C API (dylib) that exposes a setter and getter API for the last error message:

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

The simplest way of wrapping them is as follows

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

This seems to work correctly getting the last error message. However, setting the last error message seems naive and it clutters previous messages in the stack frame!

For example;

let msg: &'static str = "invalid";
MyError::set_last(msg);
println!("Last error msg: {}", MyError::get_last());

outputs Last error msg: invalidLast error msg, or

assert_eq!(MyError::get_last().trim(), msg);

fails with

thread 'tests::set_error' panicked at 'assertion failed: `(left == right)`
  left: `"invalidassertion failed: `(left == right)`\n  left: ``,\n right: ``"`,
 right: `"invalid"`'

What's a correct way doing this?

I thought of using backtrace, but found little explanations in the crate and it led to nowhere!

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ehsan M. Kermani
  • 912
  • 2
  • 12
  • 26

1 Answers1

6

Rust strings are not 0-terminated, however, you assume they are when you do:

c_api::SetLastError(msg.as_ptr() as *const c_char);

Because C APIs detect the end of a string by the presence of a null byte.

A proper way to do this would be:

let c_string = CString::new("message").unwrap(); // will add a null byte
unsafe {
    c_api::SetLastError(c_string.as_ptr());
}

Depending on whether the C API makes a copy of the string or not, you might want to use into_raw plus proper deinitialization handling if necessary.

ozkriff
  • 1,269
  • 15
  • 23
mcarton
  • 27,633
  • 5
  • 85
  • 95