Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Copy a &str into an array pointer in Rust

Tags:

rust

ffi

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 immutable
  • Using CString 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 array
  • I could use std::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 it

Is there an elegant way to do this?

like image 515
Migwell Avatar asked Oct 25 '25 14:10

Migwell


1 Answers

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');
}
like image 84
Angelicos Phosphoros Avatar answered Oct 27 '25 07:10

Angelicos Phosphoros