How to pass `*mut *mut ::std::os::raw::c_char` to C function?

I'm trying to create some Rust bindings for a C library. This library has a function that takes a char **:

foo(char **s)
{
    if (s != NULL) *s= "test";
}

I'm using bindgen to generate the Rust bindings and the function looks like this:

extern "C" {
    pub fn foo(
        s: *mut *mut ::std::os::raw::c_char,
    );
}

I'm having some troubles understanding how I can call this function from Rust.

If I go with let mut s: *const c_char = std::mem::uninitialized(); and call it like foo(&mut s); the compiler complains that the types differ in mutability:

 expected raw pointer `*mut *mut i8` found mutable reference `&mut *const i8`

How should I do this? Also, where in the Rust book (or other docs) is this explained in more details? I'm struggling to find examples of something similar.

Try this:

let mut s: *mut c_char = std::ptr::null_mut();
foo(&mut s);

Edit: NVM I misread Alice's answer, it's same code.
Or more practically like this.

This pattern is called "out parameter" in C.

Uh, what's the difference?

1 Like

This works. Thanks! How should I convert this to a Rust String? I can do it like this, but it feels like I'm doing a lot of extra steps:

let s = unsafe { std::ffi::CStr::from_ptr(s) };
let s = s.to_owned();
let s = String::from_utf8(s.to_bytes().to_vec()).unwrap();

This should do it:

let c_str = unsafe { std::ffi::CStr::from_ptr(s) };
let s = c_str.to_str().unwrap().to_string();
1 Like

Thanks again. I somehow missed the to_str() method :man_shrugging:

Of course, if foo allocates memory, then you should make sure to remember to deallocate it after copying it into the String.

Fortunately while the actual interface is more complex than my contrived example I don't have to free the string I get from this call (it is a static C string).

1 Like

In that case, FWIW, the C code didn't get the const correctness right; it should have been a char const* *:

void foo (char const* * s)
{
    if (s != NULL) { *s = "test"; }
}
2 Likes

You're right. I am not allowed to write to that string in any way, but this is not expressed by the C interface (yet). This also changes the Rust definition to *mut *const ::std::os::raw::c_char which in turn means that I should use *const c_char instead of *mut c_char.

1 Like

For real code I would normally use std::mem::MaybeUninit to make it clear that foo() is initializing your s string instead of initializing s with std::ptr::null_mut() (or std::mem::uninitialized() which is almost always broken).

// Create an uninitialized *mut c_char 
let mut s: MaybeUninit<*mut c_char> = MaybeUninit::uninit();

// Give foo() a pointer to our "s" so it can be initialized
foo(s.as_mut_ptr());

// The string pointer has now been initialized
let s: *mut c_char = s.assume_init();

Also, as you've mentioned foo()'s signature should really take a const char **. String literals are normally loaded into readonly memory and accidentally modifying them is UB. Using a *mut *const c_char means downstream code will need to explicitly cast away the const if they want to mutate it, which should trigger alarm bells.