8

I have a FFI signature I need to implement:

pub unsafe extern fn f(header_size: u32, header_ptr: *mut u8) -> i32;

A FFI caller is expected to provide a buffer header_ptr and the size of that buffer header_size. Rust is expected to fill a string into that buffer up to header_size, and return 0 if successful. The FFI caller is expected to interpret the string as ASCII.

How can I fill that buffer the most idiomatic way, given I have a headers: &str with the content I want to provide?

Right now I have:

let header_bytes = slice::from_raw_parts_mut(header_ptr, header_size as usize);

if header_bytes.len() < headers.len() { return Errors::IndexOutOfBounds as i32; }

for (i, byte) in headers.as_bytes().iter().enumerate() {
    header_bytes[i] = *byte;
}

But that feels wrong.

Edit, I think this is not an exact duplicate to this because my question relates to strings, and IIRC there were special considerations when converting &str to CStrings.

2
  • 1
    Looks like it gets the job done. Why does it feel wrong? Commented Jul 13, 2018 at 8:34
  • @kazemakase, updated my question, because there might be issues converting &str to C strings. Commented Jul 13, 2018 at 8:59

1 Answer 1

11

Since C strings are not much more than 0-terminated byte arrays converting from Rust strings is very straight forward. Almost every valid Rust string is also a valid C string, but you have to make sure that the C string ends with a 0-character and that there are no 0-characters anywhere else in the string.

Rust provides a type that takes care of the conversion: CString.

If your input string was successfully converted to a CString you can simply copy the bytes without worrying about the details.

use std::slice;
use std::ffi::CString;

pub unsafe extern fn f(header_size: u32, header_ptr: *mut u8) -> i32 {
    let headers = "abc";
    let c_headers = match CString::new(headers) {
        Ok(cs) => cs,
        Err(_) => return -1,  // failed to convert to C string
    };
    let bytes = c_headers.as_bytes_with_nul();

    let header_bytes = slice::from_raw_parts_mut(header_ptr, header_size as usize);
    header_bytes[..bytes.len()].copy_from_slice(bytes);

    0  // success
}

fn main() {
    let mut h = [1u8; 8];

    unsafe {
        f(h.len() as u32, h.as_mut_ptr());
    }

    println!("{:?}", h);  // [97, 98, 99, 0, 1, 1, 1, 1]
}

Note that I left out the length check for brevity. header_bytes[..bytes.len()] will panic if the buffer is too short. This is something you will want to avoid if f is called from C.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for showing how match CString::new should be used in this context.
slice::from_raw_parts_mut(buf as *mut u8 for buf: *mut i8 aka buf: *mut libc::c_char

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.