C FFI Memory Leak: Take ownership of allocated memory in C/C++

Hello everybody,

I'm trying to follow this tutorial (String Arguments - The Rust FFI Omnibus) to wire a C++ library to Rust.
However in my case I would need to dynamically create the string object on the C++ and want to take ownership of the memory on the Rust side.

Basically something like this:

mylib.cpp

#include <iostream>

    char* hello(){
        string SS; 
        SS = "This is a string"
        auto result = new char[SS.size() + 1];   //+1 to take account for \0 
        memcpy(result, SS.c_str(), SS.size() + 1);
        return result;
        }

main.rs

        extern crate libc;
        use libc::c_char;
        use std::ffi::CStr;
        use std::str;

        extern {
            fn hello() -> *const c_char;
        }

        fn main() {
            let c_buf: *const c_char = unsafe { hello() };
            let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
            let str_slice: &str = c_str.to_str().unwrap();
            let str_buf: String = str_slice.to_owned();  // data gets copied and memory owned by Rust
        
          //missing: free memory allocated by C++, like libc::free(c_buf)`
        }

I've two problems with the code:
a) as it is currently implemented I leak memory as result never gets deallocated. There should be a way to trigger memory dealloc?
b) is there a way to take-over the ownership of result in Rust without the need to copy the data (like .to_owned() does)?

Thanks a lot!
Best,
Stefan

1 Like

I don't think it's possible to free memory allocated with C++ new[] using libc::free directly. Since there's no way to call delete[] directly from Rust, the easiest way to free the string, if it's allocated on the C++ side, is to expose another string_free function from the C++ side.

E.g.

mylib.cpp:

extern "C" void free_string(char const *str) {
    delete[] str;
}

main.rs:

extern "C" fn free_string(str: *const c_char);

fn main() {
    // ...
    free_string(c_buf);
}

So that just covers how to free the memory.

IMO, though, the better strategy is to expose an interface from C++ that returns the length of the String, then allocate a Vec on the Rust side, pass that buffer to C++, have it fill out the buffer, and then construct the CString using CString::from_vec_unchecked. Then the memory will be freed when the string goes out of scope, and you don't have to deal with using the C++ allocator - you can stay purely in the Rust allocator.

IMO, the most sane solution is to do something like this:

  • Create a Vec<c_char> in rust. Fill it up to a set size with zeros. (or a requested size that it can obtain somehow from the C++ api)
  • Give the C++ code the buffer size and a *mut c_char pointing to the data, and let it write its string.
  • UTF8-validate it into a String
    • You can start with CStr::from_bytes_with_nul(&buffer[..strlen + 1]).to_str().

If doing this, absolutely do not use a CString to back the memory of your buffer because it is undefined behavior to let a CString be manipulated in a way that changes the position of its first null byte.