Transmute `fn` that takes `#[repr(transparent)]`

Related: Transmuting types with repr(transparent) parameters

Is it legal (not working right now) to transmute a fn that takes a struct that is #[repr(transparent) of something to a fn that takes this something?


type TypeA = *mut c_void;

struct TypeB(TypeA);

type FnA = extern "C" fn(TypeA);
type FnB = extern "C" fn(TypeB);

let f: FnB;
let f2: FnA = unsafe { std::mem::transmute(f) }; // Is this legal?

Edit: My functions are extern "C" so a stable ABI is guaranteed.

This sounds quite similar to another issue raised against the Unsafe Code Working Group repo about transmuting a function that never returns.

Maybe the discussion there will help answer your question.

Not exactly, as my case is easier and also my functions are extern "C", so a stable ABI is guaranteed (updated the question).

@chrefr yes, and it is precisely the point of using #[repr(transparent)] over #[repr(C)]: it is valid to reinterpret one function signature as the other, but it may not be safe: for the conversion to be safe, you need to make sure the function is only fed a parameter which was safe (and a fortiori, valid) to transmute to the other type.

That is, given #[repr(transparent)] struct B(A); (or vice versa),

then transmuting an extern "ABI" fn(A, OtherArg) -> Ret to an extern "ABI" fn(B, OtherArg) -> Ret is:

  • always valid;

  • safe / "correct" (no-UB) provided such a function is only called on instances of B which are safe to transmute to A (or just valid to transmute, if you happen to know that the body of the function does not rely on safety invariants…).

  • sound (to expose to/through an API) provided any instance of type B, is safe /sound to transmute to A;

To better see the corner cases, consider:

extern "ABI" fn foo (n: NonZeroU8) { … }

and then doing

let f =
        extern "ABI" fn(NonZeroU8),
        extern "ABI" fn(u8),  // `NonZeroU8` is a `#[repr(transparent)]` wrapper around `u8`

That is valid, but calling f(0) will cause UB since it would break the validity invariant of NonZeroU8 inside foo's body. So you cannot expose such a transmuted function pointer to/through a "public" API (unless you mark the resulting function pointer as unsafe), but you can safely use it yourself provided you make sure not to call it on 0.