Global allocator replaceable from C

When writing a C API for a Rust library, what's a good way to expose an interface for downstream C code to replace Rust's global allocator with its own callbacks?

2 Likes

if you want to replace the global allocator at link time, you can just write your own GlobalAlloc in rust and the implementation then just calls some ffi functions.

// these must be provided by C library
extern "C" {
    fn my_alloc(size: usize, align: usize) -> *mut u8;
    fn my_free(ptr: *mut u8);
}
struct MyGlobalAllocator {};
impl GlobalAlloc for MyGlobalAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let size = layout.size();
        let align = layout.align();
        // delegate to ffi funciton `my_alloc`
        my_alloc(size, align)
    }
    unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {
        // delegate to ffi function `my_free`
        my_free(ptr);
    }
}
#[global_allocator]
static ALLOCATOR: MyGlobalAllocator =  MyGlobalAllocator {};

on the other hand, if you want to replace it at runtime, DO NOT DO THIS.

instead, you can make your data type Allocator aware, or use the Allocator aware APIs of the standard library, e.g. Vec::new_in() instead of Vec::new(). the Allocator API needs nightly toolchain though.

3 Likes

Can you elaborate on why this might be a bad idea?

Is it because the standard library does some allocations before main or for thread-local variables, so by changing the global allocator you could end up with a world where a piece of memory might be allocated by one allocator and deallocated by another?

1 Like

sorry, I don't know If there's allocation or not before main. as for thread local variables, it's very platform specific and often involve some kind of low level allocation, but I don't think they go through the allocator API, and I believe rust thread local variables are lazily constructed on first access, the LocalKey type itself should not need allocation though.

yes, that's what I was thinking.

I was commenting on the question of OP wanting the global allocator to be replaceable from C, and that's extreme dangerous to do at runtime, unless you can guarantee there's absolutely no allocation happened at the point you change the global allocator (which may be easy, or may not be possible at all, depending how the final application mixing rust and C).

1 Like

Thanks. My original thought was to use the "caller must provide definition for symbol" approach with #[global_allocator], but I realized this has a poor user experience by default and isn't great for using the FFI interface from another Rust crate. So I'm not going to touch the global allocator in my FFI code; anybody who wants to call the library from C and make it use a custom allocator can write a tiny shim library crate that looks like

pub use my_library::ffi::*;

#[global_allocator]
static ALLOCATOR: CustomAllocator = CustomAllocator;

struct CustomAllocator;

impl GlobalAlloc for CustomAllocator {
    // as in nerditation's post
}

and then compile that as staticlib or cdylib.