Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

casting

rust

ffi

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.

like image 440
Ryan C. Avatar asked May 17 '18 06:05

Ryan C.


1 Answers

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

like image 176
Jorge Israel Peña Avatar answered Oct 04 '22 21:10

Jorge Israel Peña