Search code examples
castingrustffi

Why does casting from a reference to a c_void pointer require a double cast?


When working with Foreign Function Interfaces (FFIs), I regularly see a double cast from reference-to-pointer-to-struct to pointer-to-pointer-to-void. For example, given an FFI-like function:

unsafe fn ffi(param: *mut *mut c_void) {}

The way to call this is:

struct foo;

let mut bar: *mut foo = ptr::null_mut();
unsafe { ffi(&mut bar as *mut *mut _ as *mut *mut c_void); }

Removing the intermediate cast yields this error:

error[E0606]: casting `&mut *mut foo` as `*mut *mut winapi::ctypes::c_void` is invalid
  --> src\main.rs:36:18
   |
36 |     unsafe { ffi(&mut bar as *mut *mut c_void); }
   |

I tried to get the compiler to tell me what the intermediate type is by forcing it into an obviously wrong type:

let mut bar: *mut foo = ptr::null_mut();
let mut test: u8 = &mut bar as *mut *mut _;

Which resulted in this error:

error[E0308]: mismatched types
  --> src\main.rs:36:24
   |
36 |     let mut test: u8 = &mut bar as *mut *mut _;
   |                        ^^^^^^^^^^^^^^^^^^^^^^^ expected u8, found *-ptr
   |
   = note: expected type `u8`
              found type `*mut *mut _`

But *-ptr doesn't seem to be an actual type that I can put in place of _. Why is the intermediate as *mut *mut _ required, and what is the inferred type?

I found this question which is related (Working with c_void in an FFI) but it doesn't actually explain anything about the double cast.


Solution

  • If you have:

    let mut bar: *mut foo = ptr::null_mut();
    

    Then you take &mut bar, the type is &mut *mut foo. But you need *mut *mut foo, so you can simply coerce it by doing &mut *mut foo as *mut *mut _, where _ is inferred as foo (try typing it explicitly: *mut *mut foo). Once you have it as a raw pointer, then you are able to cast to *mut *mut c_void.

    So to recap, the double cast is necessary to first coerce from a reference to a raw pointer, then from a raw pointer cast to a c_void, because you otherwise normally can't cast straight from a reference to a raw c_void pointer.

    Fully typed example:

    let mut bar: *mut foo = std::ptr::null_mut();
    
    let mut_ref: &mut *mut foo = &mut bar;
    
    let raw_ptr: *mut *mut foo = mut_ref as *mut *mut _;
    
    let void_cast: *mut *mut c_void = raw_ptr as *mut *mut c_void;
    
    unsafe { ffi(void_cast); }
    

    Playground