Poperton
Poperton

Reputation: 2148

Is it possible to receive a pointer to C function in Rust and call it back?

I'm learning about interfacing Rust and C++ (using the C interface). This is an example from Mozilla's guide for Rust on Android:

use std::os::raw::{c_char};
use std::ffi::{CString, CStr};

#[no_mangle]
pub extern fn rust_greeting(to: *const c_char) -> *mut c_char {
    let c_str = unsafe { CStr::from_ptr(to) };
    let recipient = match c_str.to_str() {
        Err(_) => "there",
        Ok(string) => string,
    };

    CString::new("Hello ".to_owned() + recipient).unwrap().into_raw()
}

as you can see, a C char pointer is expected, and it returns a C char pointer also.

I want to use a Rust code as a library on my project. This Rust code will deliver packets to me when they're ready. Therefore, I need a way to pass a callback to Rust so it can call it back when it has data. Kinda like this C++ snippet:

extern "C" onData(uint8_t* payload, size_t size) {
    //do something with payload
}

MyRustType myRustObject("initialization_string");
myRustObject.setOnDataCallback(&onData);
myRustObject.beginWork();

So, the rust 'class', MyRustType, will begin to work and deliver packets through onData. Is it possible to pass a C function as a pointer to Rust?

If not, I can pass the pointer as an uint64_t number, and make Rust pass this number + payload back to a C function, which in turn casts this uint64_t pointer into the function and calls it with the payload. But I think that since Rust is so C-friendly, there's a better way of doing this.

Upvotes: 4

Views: 2824

Answers (1)

rodrigo
rodrigo

Reputation: 98368

You can call freely, but unsafely, from C to Rust and from Rust to C.

From Rust to C: write a extern "C" block with the prototypes of the C functions.

From C to Rust: write the Rust function with a #[no_mangle] extern "C" prefix. You must ensure that any value received or returned is FFI friendly: basic types or repr(C) types or raw pointers to those and a few others.

Also you will need the equivalent C declarations to those of Rust. for that you can use the types in std::os::raw, std::ffi and the external crate libc.

Particularly, the answer to your questions about function pointers, a type such as this in C:

typedef int (*callback)(uint8_t *data, size_t len);

is translated into Rust as:

type Callback = unsafe extern "C" fn(data: *mut u8, len: usize) -> c_int;

This type Callback is FFI friendly, so you can store it, pass between Rust and C, and call it whenever you want. And of course you can use it as argument or return type in other FFI declarations:

int do_the_thing(uint8_t*data, size_t len, callback cb);
#[no_mangle]
pub extern "C" fn do_the_thing(
    data: *const u8, len: usize,
    cb: Option<Callback>) -> c_int)
{
    //...
}

Remember that in Rust pointer functions are non-nullable, so if you want C to be able to pass NULL you use Option<fn>.

For more details about FFI in Rust you can read this chapter of the Rustonomicon.

Upvotes: 9

Related Questions