Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is is safe to create a Rust String using a malloced string?

I am working with a C API that returns a malloced string:

char *foo(int arg);

Can I reuse that memory in Rust code without O(n) copying?

let p: *mut libc::c_char = foo(42);
let len = strlen(p);
let s: String = String.from_raw_parts(p, len, len);

The documentation says

The memory at ptr needs to have been previously allocated by the same allocator the standard library uses.

I failed to find what allocator is used by standard library.

like image 439
KAction Avatar asked Dec 14 '22 10:12

KAction


1 Answers

In general, it's not safe to create a String from a string that was not allocated from Rust.

Rust 0.11.0 through 1.31.1 used jemalloc. Rust 1.32.0 changed to use the system's default allocator.

Additionally, Rust 1.28.0 introduced a mechanism that applications can use to replace the global allocator with one of their choosing.

It's important to note that, although Rust now uses the system's default allocator by default, that doesn't mean that C libraries use the same allocator, even if it's literally malloc. For example, on Windows, if you use a C library that's been compiled with Visual C++ 2008 while your Rust binary has been compiled with Visual Studio 2019 Build Tools, there will be two C runtime libraries loaded in your process: the C library will use msvcr90.dll while your Rust binary will use ucrtbase.dll. Each C runtime library manages its own heap, so memory allocated by one cannot be freed by the other.

A well-designed C library ought to provide a function to free resources for each type of resource that the library may allocate itself. Functions that return pointers or handles to such allocations ought to document which function should be called to free the resource(s). See this other question regarding usage of LLVM's C API for an example of a well-designed API.

Perhaps you don't actually need a String? Consider using CStr instead, if that's possible. A CStr is akin to a str, so it's just a view into memory and it doesn't care how it was allocated, but it's more permissive than str. You can convert a CStr to a str using CStr::to_str (the CStr must contain a UTF-8 string for the conversion to succeed).

If there is indeed a function in the library to free the string, you might also want to write a wrapper struct that will handle deallocation automatically and will deref to CStr. This struct would represent an owned string, akin to String or CString, but with memory managed by the library instead of Rust's global allocator. For example:

extern crate libc; // 0.2.62

use std::ffi::CStr;
use std::ops::Deref;

extern {
    fn libfoo_free(string: *mut libc::c_char);
}

struct LibfooString(*mut libc::c_char);

impl Drop for LibfooString {
    fn drop(&mut self) {
        unsafe {
            libfoo_free(self.0);
        }
    }
}

impl Deref for LibfooString {
    type Target = CStr;

    fn deref(&self) -> &Self::Target {
        unsafe {
            CStr::from_ptr(self.0)
        }
    }
}
like image 65
Francis Gagné Avatar answered Dec 19 '22 12:12

Francis Gagné