What is the correct way to fill a C string pointer from Rust?
Asked Answered
K

1

7

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.

Kommunarsk answered 13/7, 2018 at 8:20 Comment(2)
Looks like it gets the job done. Why does it feel wrong?Erythroblast
@kazemakase, updated my question, because there might be issues converting &str to C strings.Kommunarsk
E
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.

Erythroblast answered 13/7, 2018 at 9:22 Comment(1)
Thanks for showing how match CString::new should be used in this context.Kommunarsk

© 2022 - 2024 — McMap. All rights reserved.