I have an FFI function that allocates and returns a *mut ::std::os::raw::c_char which points to an array of known length. I also have a s: &str which is a Rust string. I simply want to copy the contents of s into the char pointer with added null byte, kind of like a strcpy() in C, but I can't find an elegant way to do this in Rust. As a bonus, I'd love to do this in only one copy, without an intermediate allocation, but if this isn't possible then that's okay.
Here are my variables:
let ptr: *mut ::std::os::raw::c_char;
let s: &str;
I have considered:
std::ffi::CStr::from_ptr to make a CStr representing the allocated memory, but this seems to be immutableCString but this seems to be for strings that Rust "owns" so I'm not sure it's applicable here, when Rust isn't responsible for freeing this arraystd::slice::from_raw_parts_mut to create a mutable slice from ptr, but this is a bit low level, I was hoping for a higher level string manipulation option that adds the null for me, rather than making me do itIs there an elegant way to do this?
Well, it requires unsafe because you write to some unknown pointer.
The code below assumes that C uses UTF-8 encoding.
Note, that I uses memcpy intentionally instead of making a slice from foreign pointer because there is no guarantee that memory by that pointer is initialized and making a slice from unintialized memory is undefined behaviour.
#![forbid(unsafe_op_in_unsafe_fn)]
use std::os::raw::c_char;
/// # Safety
/// `arr` must point to some valid memory which doesn't overlap with `s`.
/// `arr` must point to at least `known_len` bytes.
unsafe fn copy_rust_str_to_c_arr(s: &str, arr: *mut c_char, known_len: usize) {
// Note, that panics must not be running across FFI boundaries
// because it would be undefined behaviour.
// But writing more bytes than allocated is also UB.
assert!(s.len() + 1 <= known_len, "Not enough memory to copy str");
// This is true for any modern architecture.
assert_eq!(std::mem::size_of::<c_char>(), 1);
unsafe {
let our_ptr = s.as_bytes().as_ptr();
// Safety: our_ptr is valid because it produced from string reference.
// `arr` must be valid by safety requirements of function.
// We checked length right before.
std::ptr::copy_nonoverlapping(our_ptr, arr.cast(), s.len());
// Set zero terminator for C.
*arr.add(s.len()) = b'\0' as _;
}
}
#[test]
fn test_copy() {
use std::ffi::{CStr, CString};
let s = "Hello, World!";
// This would be used as a pointer from C.
let mut dest: Vec<c_char> = vec!['\t' as _; s.len() * 2];
unsafe {
copy_rust_str_to_c_arr(s, dest.as_mut_ptr(), dest.len());
}
let dest = unsafe { CStr::from_ptr(dest.as_ptr()) };
let expected = CString::new(s).unwrap();
assert_eq!(&*expected , dest);
}
Also, nightly version which compiles to more-or-less same machine code.
Making a slice of MaybeUninits is fine even if data by pointer is not initialized.
#![feature(maybe_uninit_write_slice)]
#![forbid(unsafe_op_in_unsafe_fn)]
use core::mem::MaybeUninit;
use std::os::raw::c_char;
/// # Safety
/// `arr` must point to some valid memory which doesn't overlap with `s`.
/// `arr` must point to at least `known_len` bytes.
unsafe fn copy_rust_str_to_c_arr(s: &str, arr: *mut c_char, known_len: usize) {
// Note, that panics must not be running across FFI boundaries
// because it would be undefined behaviour.
// This is true for any modern architecture.
assert_eq!(std::mem::size_of::<c_char>(), 1);
let target: &mut [MaybeUninit<u8>] =
unsafe { std::slice::from_raw_parts_mut(arr.cast(), known_len) };
MaybeUninit::write_slice(&mut target[..s.len()], s.as_bytes());
// Set zero terminator for C.
target[s.len()] = MaybeUninit::new(b'\0');
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With