[FFI] Function pointers and transmutation

    unsafe fn instance_import<T>(name: *const u8) -> Option<T> {
        match vkGetInstanceProcAddr(std::ptr::null_mut(), name as *const i8) {
            Some(ptr) => {
                return Some(std::mem::transmute(ptr));
            }

            None => {
                return None
            }
        }
    }

Hello, can anyone with knowledge of the black magic known as FFI help me with transmutation of function hands?

In the code below I use a generic function to get the pointer to an instance function and return an option with the original type converted to the destination.
But the compiler says that the destination type has a variable size, but the type is known and has a fixed size, 64 bits.
The strangest thing is that in the warning message it does not inform the destination type.

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types

16 |                 return Some(std::mem::transmute(ptr));
   |                             ^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: `unsafe extern "C" fn()` (64 bits)
   = note: target type: `T` (this type does not have a fixed size)

Origin and destine types.


pub type PTR_vkVoidFunction = unsafe extern "C" fn();

pub type PTR_vkCreateInstance = 
    unsafe extern "C" fn(
        pCreateInfo: *const VkInstanceCreateInfo,
        pAllocator: *const VkAllocationCallbacks,
        pInstance: *mut VkInstance,
    ) -> VkResult
;

NEW:

    unsafe fn instance_import(name: *const u8) -> Result<PTR_vkVoidFunction> {
        match vkGetInstanceProcAddr(std::ptr::null_mut(), name as *const i8) {
            Some(ptr) => {
                return Ok(ptr);
            }

            None => {
                return Err(LoadError);
            }
        }
    }

///...
vkCreateInstance: std::mem::transmute(Self::instance_import("vkCreateInstance\0".as_ptr())?)
          

Moving transmute out of the function makes the code compile without problems.

It does tell you what the destionation type is. It inferred it to be the generic type T, i.e. whatever type the caller of instance_import decided it should be. Since the caller can choose whatever, the size is not known.

Maybe you needed to read the pointer or something? It's not entirely clear what it's supposed to be.

Destine type:

pub vkCreateInstance: PTR_vkCreateInstance

Origin type:

PTR_vkVoidFunction

Both are pointers to functions, it has 64 bits, but the generic function does not recognize the destination type, even using the turbofish.
I need to load function pointers, which are returned as void(*) (void) by the loader and convert them to the correct type.

Okay but T might not be PTR_vkCreateInstance. Try being explicit with the types:

transmute::<PTR_vkVoidFunction, PTR_vkCreateInstance>(ptr)

Then you will probably get a different error, but it might be easier to understand that one.

1 Like
error[E0308]: mismatched types
  --> src/vulkan_loader.rs:30:27
   |
27 |     unsafe fn instance_import2<T>(name: *const u8) -> Result<T> {
   |                                - this type parameter
...
30 |                 return Ok(std::mem::transmute::<PTR_vkVoidFunction, PTR_vkCreateInstance>(ptr));
   |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found fn pointer
   |
   = note: expected type parameter `T`
                  found fn pointer `unsafe extern "C" fn(*const vulkan::VkInstanceCreateInfo, *const VkAllocationCallbacks, *mut *mut VkInstance_T) -> i32`

What if I called instance_import2::<std::fs::File>? Then the return type is Result<File>, but that's certainly not what you are returning. Change the return type to the actual thing you are trying to return.

2 Likes
vkCreateInstance2: Self::instance_import2::<PTR_vkCreateInstance>("vkCreateInstance\0".as_ptr())?

The same error message as before, this is very strange.
Do you want to take a look at the code? I go up on gitlab

When you use generics, the compiler enforces that you code is valid for all possible choices of types that satisfy the trait bounds (you have no trait bounds). If you code would not be valid for all possible types in place of T, it will not compile.

It doesn't just look at what types you call it with.

3 Likes

This is bad, it is a code produced by bindgen, there is no way to use bounds with them.

Is instance_import2 produced by bindgen?

1 Like

No but the types used are, I tried to use Sized and it didn't work, the compiler insists that T has no known size.

The types are not the problem, so the bindgen code is no problem.

The thing I'm trying to get you to do is this:

unsafe fn instance_import(name: *const u8) -> Option<PTR_vkCreateInstance> {
    match vkGetInstanceProcAddr(std::ptr::null_mut(), name as *const i8) {
        Some(ptr) => {
            return Some(std::mem::transmute(ptr));
        }

        None => {
            return None
        }
    }
}
2 Likes

The compiler is right. For any fixed choice of T, the size might be fixed, but T could be anything, so you don't know what that fixed size is.

2 Likes

This works, so is it certain that I can't use instance_import to transmute types using generic?

It's possible that you could convince the compiler to just ignore any problems, which would of course cause very bad things to happen if it called with a bad type T.

However I would try to avoid it.

1 Like

Ok, thanks for the clarification.

@rafaelcout if you still want to use generics, you need to express that you will only be using function pointers as the type T. This is done by using a trait, and implementing it for all the function pointers you want to be using:

unsafe
trait IsFunctionPointer : Sized {
    /// ## Safety
    ///
    /// The input `PTR_vkVoidFunction` must point to a valid function
    /// pointer of type `Self`.
    unsafe
    fn transmute_from_void_function (
        _: PTR_vkVoidFunction,
    ) -> Self;
}

impls!( _5, _4, _3, _2, _1 );
macro_rules! impls {(
    $( $_0:ident $(, $_k:ident)* $(,)? )?
) => (
    $( impls!( $($_k),* ); )?

    unsafe
    impl<Ret, $($_0 $(, $_k)*)?> IsFunctionPointer
        for unsafe extern "C" fn($($_0 $(, $_k)*)?) -> Ret
    {
        unsafe
        fn transmute_from_void_function (
            ptr: PTR_vkVoidFunction,
        ) -> Self
        {
            ::core::mem::transmute(ptr)
        }
    }
)} use impls;

And now you can write:

  unsafe
  fn instance_import<T> (name: *const u8) -> Option<T>
+ where
+     T : IsFunctionPointer,
  {
      match vkGetInstanceProcAddr(std::ptr::null_mut(), name as *const i8) {
          Some(ptr) => {
-             return Some(std::mem::transmute(ptr));
+             return Some(T::transmute_from_void_function(ptr));
          },
          None => {
              return None
          },
      }
  }
3 Likes