Exposing a simple struct using Rust FFI
First of all, you should always run clippy:

error: this public function dereferences a raw pointer but is not marked `unsafe`
  --&gt; src/lib.rs:20:29
   |
20 |         ffi::CStr::from_ptr(text)
   |                             ^^^^
   |
   = note: #[deny(clippy::not_unsafe_ptr_arg_deref)] on by default
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref

error: calls to `std::mem::drop` with a value that implements Copy. Dropping a copy leaves the original intact.
  --&gt; src/lib.rs:60:4
   |
60 |    mem::drop(data); 
   |    ^^^^^^^^^^^^^^^
   |
   = note: #[deny(clippy::drop_copy)] on by default
note: argument has type *mut std::ffi::c_void
  --&gt; src/lib.rs:60:14
   |
60 |    mem::drop(data); 
   |              ^^^^
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy


drop(data) is indeed totally useless here pointer don't have drop implementation, you must use Box::from_raw(data as *mut SomeData) (BTW you didn't check for null here hope this is what you want). I also advise you to mark these function as unsafe.

For the style there is two sides as you seem to require the pointer is no null:


You could assume the pointer is always no null, add only a debug panic
You could panic if the pointer is null


I'm in favor of the first option but it's as you like, if you take the second option I advice to use Option&lt;&amp;T&gt; this will prevent you to forget to check if the pointer is null, it's guarantee by Rust that Option&lt;&amp;c_char&gt; will be None for null pointer value.

Your get_number() and get_text() use Box::from_raw(data as *mut SomeData) for nothing, just use directly the pointer or transform it to reference.

I'm unsure void pointer is necessary.


  Do I need to provide a free_string function? Is there a way to just return a pointer into the Rust string?


yes of course you must use from_raw()

Final code:

use std::ffi;
use std::os::raw::{c_char, c_void};

#[derive(Debug)]
pub struct SomeData {
    text: String,
    number: usize,
}

#[no_mangle]
pub unsafe extern "C" fn make_some_data(text: Option&lt;*const c_char&gt;, number: usize) -&gt; *mut c_void {
    ffi::CStr::from_ptr(text.unwrap())
        .to_str()
        .map(|str| {
            Box::into_raw(Box::new(SomeData {
                text: str.to_string(),
                number,
            }))
        })
        .unwrap() as *mut c_void
}

#[no_mangle]
pub unsafe extern "C" fn get_text(data: Option&lt;&amp;c_void&gt;) -&gt; *mut c_char {
    let data = my_real_data(data);
    ffi::CString::new(data.text.clone()).unwrap().into_raw()
}

#[no_mangle]
pub unsafe extern "C" fn get_number(data: Option&lt;&amp;c_void&gt;) -&gt; usize {
    let data = my_real_data(data);
    data.number
}

unsafe fn my_real_data(data: Option&lt;&amp;c_void&gt;) -&gt; &amp;SomeData {
    &amp;*(data.unwrap() as *const std::ffi::c_void as *const SomeData)
}

#[no_mangle]
pub unsafe extern "C" fn drop_data(data: Option&lt;*mut c_void&gt;) {
    Box::from_raw(data.unwrap() as *mut SomeData);
}

#[no_mangle]
pub unsafe extern "C" fn drop_text(data: Option&lt;*mut c_char&gt;) {
    ffi::CString::from_raw(data.unwrap());
}