3

I've got a C function that (simplified) looks like this:

static char buffer[13];

void get_string(const char **s) {
    sprintf(buffer, "Hello World!");
    *s = buffer;
}

I've declared it in Rust:

extern pub fn get_string(s: *mut *const c_char);

But I can't figure out the required incantation to call it, and convert the result to a Rust string. Everything I've tried either fails to compile, or causes a SEGV.

Any pointers?

Roger Lipscombe
  • 89,048
  • 55
  • 235
  • 380
  • 1
    [this](https://play.rust-lang.org/?gist=578f81f8830f7d6cb7dc3224ddfe6ab0&version=stable&mode=debug) should work, see [this](https://stackoverflow.com/questions/24145823/how-do-i-convert-a-c-string-into-a-rust-string-and-back-via-ffi/24148033#24148033) answer for more information, maybe a duplicate ? – Stargateur May 20 '18 at 18:32
  • 1
    The linked question _returns_ a string, rather than using an out param, so I couldn't see how to adapt it. Your example seems to work; post it as an answer, maybe? – Roger Lipscombe May 20 '18 at 18:51
  • *Everything I've tried* — would you care to show some of those attempts? – Shepmaster May 20 '18 at 19:29
  • Combining [Passing a Rust variable to a C function that expects to be able to modify it](https://stackoverflow.com/q/42727935/155423) and [How do I convert a C string into a Rust string and back via FFI?](https://stackoverflow.com/q/24145823/155423) should be sufficient. – Shepmaster May 20 '18 at 19:30
  • 1
    Yeah; they're _not_ sufficient. There's a difference between a `char **` argument and a `char *` return value, and also between an `int *` argument and ` char **` argument. I'm new to Rust; I have _no idea_ how to combine them. – Roger Lipscombe May 21 '18 at 07:08
  • "would you care to show some of those attempts?"; no, because they're not relevant. It's a simple question. – Roger Lipscombe May 21 '18 at 07:11

2 Answers2

5

First of all, char in Rust is not the equivalent to a char in C:

The char type represents a single character. More specifically, since 'character' isn't a well-defined concept in Unicode, char is a 'Unicode scalar value', which is similar to, but not the same as, a 'Unicode code point'.

In Rust you may use u8 or i8 depending in the operating system. You can use std::os::raw::c_char for this:

Equivalent to C's char type.

C's char type is completely unlike Rust's char type; while Rust's type represents a unicode scalar value, C's char type is just an ordinary integer. This type will always be either i8 or u8, as the type is defined as being one byte long.

C chars are most commonly used to make C strings. Unlike Rust, where the length of a string is included alongside the string, C strings mark the end of a string with the character '\0'. See CStr for more information.

First, we need a variable, which can be passed to the function:

let mut ptr: *const c_char = std::mem::uninitialized();

To pass it as *mut you simply can use a reference:

get_string(&mut ptr);

Now use the *const c_char for creating a CStr:

let c_str = CStr::from_ptr(ptr);

For converting it to a String you can choose:

c_str.to_string_lossy().to_string()

or

c_str().to_str().unwrap().to_string()

However, you shouldn't use String if you don't really need to. In most scenarios, a Cow<str> fulfills the needs. It can be obtained with c_str.to_string_lossy():

If the contents of the CStr are valid UTF-8 data, this function will return a Cow::Borrowed([&str]) with the the corresponding [&str] slice. Otherwise, it will replace any invalid UTF-8 sequences with U+FFFD REPLACEMENT CHARACTER and return a Cow::[Owned](String) with the result.

You can see this in action on the Playground. This Playground shows the usage with to_string_lossy().

Community
  • 1
  • 1
Tim Diekmann
  • 7,755
  • 11
  • 41
  • 69
0

Combine Passing a Rust variable to a C function that expects to be able to modify it

unsafe { 
    let mut c_buf = std::ptr::null();
    get_string(&mut c_buf);
}

With How do I convert a C string into a Rust string and back via FFI?:

extern crate libc;

use libc::c_char;
use std::ffi::CStr;
use std::str;

extern "C" {
    fn get_string(s: *mut *const c_char);
}

fn main() {
    unsafe {
        let mut c_buf = std::ptr::null();
        get_string(&mut c_buf);
        let c_str = CStr::from_ptr(c_buf);
        let str_slice: &str = c_str.to_str().unwrap();
        let str_buf: String = str_slice.to_owned(); // if necessary
    };
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366