Expected `*const i8`, found `*const u8` for C function pointer waiting for a `const char*`

I'm trying to write Rust lib containing code that will be called from a C code:

The C code call a function providing a function pointer of a function having const char* message argument and I get an error while compiling.

Here is how I proceed:

static mut l_debug_context: Option<*mut libc::c_void> = None;
static mut l_debug_message: Option<extern fn(Context: *mut libc::c_void,
                                             level:   libc::c_int,
                                             Message: *const libc::c_char)> = None;

PluginStartup() is called from C compiled code.
I retrieve a function pointer and store it in my static variables (see bellow for the original C prototype)

#[no_mangle]
pub extern "C"
fn PluginStartup( CoreLibHandle: m64p::DynlibHandle,
                  Context:       *mut libc::c_void,
                  DebugCallback: extern fn(Context: *mut libc::c_void, // The function pointer
                                           level:   libc::c_int,
                                           Message: *const libc::c_char))
                  -> m64p::Error {

    unsafe {
        if !Context.is_null() {
            l_debug_context = Some(Context);
        }
        if !DebugCallback.is_null() {
            l_debug_message = Some(DebugCallback);
        }
    }

    return m64p::Error::Success;
}

I "wrap" the C function into a rusty one (that will be called into my rusty code):

fn debug_message(level: m64p::MsgLevel, msg: &str) {

    // convert the given rusty enum to valid integer
    let ilevel: i32 = match level {
        m64p::MsgLevel::Error   => 1,
        m64p::MsgLevel::Warning => 2,
        m64p::MsgLevel::Info    => 3,
        m64p::MsgLevel::Status  => 4,
        m64p::MsgLevel::Verbose => 5,
    };

    unsafe {
        // Retrieve context pointer
        let ctx: *mut libc::c_void = match l_debug_context {
            Some(ctx) => ctx,
            None      => return,
        };
        // Retrive function pointer and call function
        match l_debug_message {
            Some(f) => f(ctx, ilevel, msg.as_ptr()), // compiler fail on this line
            None    => (),
        }
    } // unsafe
}

And the error:

src/lib.rs:53:39: 53:51 error: mismatched types:
 expected `*const i8`,
    found `*const u8`
(expected i8,
    found u8) [E0308]
src/lib.rs:53             Some(f) => f(ctx, ilevel, msg.as_ptr()),
                                                    ^~~~~~~~~~~~

And for information, the C prototype I'm trying to reproduce:

m64p_error PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context, void (*DebugCallback)(void *Context, int level, const char *Message))

Compiler version:
rustc v1.1.0

And as a second question, I'm a quite new Rust user, am I doing things right?

A big thanks in advance!

The problem is that Rust strings are not C strings. They are not binary compatible, and blindly passing a Rust string as a C string will cause problems.

Thankfully, there's a type for that: std::ffi::CString.

3 Likes

Arf! I will loose some performance? :frowning:

I will try that!

Here is the solution:

fn debug_message(level: m64p::MsgLevel, msg: &str) {

    // convert the given enum to valid integer
    let ilevel: i32 = match level {
        m64p::MsgLevel::Error   => 1,
        m64p::MsgLevel::Warning => 2,
        m64p::MsgLevel::Info    => 3,
        m64p::MsgLevel::Status  => 4,
        m64p::MsgLevel::Verbose => 5,
    };

    // Convert Rust string to C
    let c_msg = match std::ffi::CString::new(msg) {
        Ok(s)  => s,
        Err(e) => return,
    };

    unsafe {
        // Retrieve context pointer
        let ctx: *mut libc::c_void = match l_debug_context {
            Some(ctx) => ctx,
            None      => return,
        };
        // Retrive function pointer and call function
        match l_debug_message {
            Some(f) => f(ctx, ilevel, c_msg.as_ptr()),
            None    => (),
        }
    } // unsafe
}

Thanks a lot! :smile:

Yes, but it's unavoidable. Rust strings aren't zero-terminated.

Technically, even CString isn't correct: C doesn't specify what encoding it uses, so there's no guarantee that the string's contents will be interpreted correctly by the C code. Heck, your program might be running on a system using Shift-JIS for the 8-bit encoding, for all you know. Wheeee.

Pretty much everything involving text and C/C++ is an endless field of agony and suffering.

2 Likes

Thanks Daniel.

I want to use logging to trace what the program does: The program execute a huge amount of "commands" and I want to print then with their arguments and their results. Output can be huge! C printf is quite efficient for this.

From your perspective, what could be the best way to achieve this in Rust?

Thanks in advance!

There's a log crate for Rust that gives you logging macros, and at least two crates that do the actual output (env_logger and log4rs). If that's overkill, print!/println! shouldn't be slower than printf. Actually, println! should be faster than printf, because it doesn't interpret the format string at runtime; it processes it at compile time.

1 Like

I already use log crate. I didn't bench it yet.

Wow interesting! I think it's a rule for rust macro in general. :smiley:

Thanks a lot!