How do I pass in Rust string to C function that modifies it in place?

I've made this simple C function that converts a null terminated string in place:

void make_uppercase(char *buf){
    while (1)
    {
        if (*buf == '\0') break;
        if (*buf >= 'a' || *buf <= 'z') *buf = *buf + ('A' - 'a');
        buf++;
    }
}

then I write this extern definition in Rust:

extern "C" {
    fn make_uppercase(buf: *mut u8);
}

At first I tried to use CString as_ptr method to get the pointer to null terminated string, but unfortunately its read only. I try to find as_ptr but mutable, but it doesn't exist

1 Like

You can't; the safety contract of CString requires that there be no 0 (NUL) characters within the string and that it contain a terminating 0. You could violate this contract by writing directly to the underlying buffer. The documentation explicitly mentions that you are not allowed to do so:

If you want to modify the string in place, extract the underlying buffer using into_bytes() or into_bytes_with_nul(), write to the byte buffer, and re-create the CString from the modified byte buffer.

There's into_raw which gives you the mutable pointer and makes Rust forget about the value, so it doesn't deallocate. You can then pass that to C, modify it (as long as you don't change the length or add null characters in the middle, pass it back and reconstruct a CString using from_raw.

Yes, but that's very dangerous, so I wouldn't recommend it unless there's no other option. Thus, I explicitly avoided mentioning that. It basically ignores the safety issue completely, and there will be no indication of the misuse whatsoever, compile-time nor runtime.

1 Like

I think there should be such method, the same way str::as_bytes_mut(), String::as_mut_vec() and str::as_mut_ptr() exist despite the ability to create invalid UTF-8 with them, explicitly stating that as UB.

You can convert the String to a Vec<u8> and pass a pointer to it, as well as length to C. C can then modify the data.
After it you convert it back with String::from_utf8 (_unchecked if you are really sure what you are doing).

2 Likes

I recommend using the "checked" / "guarded" str::with_mut() pattern showcased in:

let mut s = String::from("…\0");
with_str_bytes(s, |bytes| {
    unsafe { ffi::c_fun(bytes.as_mut_ptr()); }
});
println!("{}", s);

As to how to write with_str_bytes:

fn with_str_bytes<R> (s: &mut str, f: impl FnOnce(&mut [u8]) -> R)
  -> R
{
    let bytes: &mut [u8] = unsafe { s.as_bytes_mut() };
    ::unwind_safe::with_state(bytes)
        .try_eval(|bytes| f(&mut bytes[..]))
        .finally(|bytes| if let Err(err) = ::core::str::from_utf8(bytes) {
            bytes[err.valid_up_to() ..].fill(b'\0');
            panic!(
                "`with_str_bytes()`: got invalid UTF-8 at byte-index {}",
                err.valid_up_to() + 1,
            );
        })
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.