Description
A question came up on the community Discord today and I'd like to know if my reasoning was correct.
This was the original question:
Why is it impossible to cast
fn() -> !
tofn()
and would it be safe to perform the conversion with a transmute?
And the following conversation ensued:
Michael-F-Bryan: I think the only valid casts/coersions for a function are extending/shrinking lifetimes (e.g. between
fn(&'static str)
andfn<'a>(&'a str))
and non-capturing closures to function pointers.
#![cfg_attr(goat, no_std)]
: yes, they are covariant over the return type
!
is a subtype of all types, isn't it
Michael-F-Bryan: transmuting between them could be unsound because of things like Return Value Optimisation... Imagine you had a
fn() -> [u8; 256]
and LLVM decided to apply RVO, turning it intofn(&mut [u8; 256])
to elide the copy. Now you would be transmuting a function which accepts no arguments(fn() -> !
) into a function that accepts one argument, and that may mess up your stack/registers depending on your calling convention.
just hypothesizing
#![cfg_attr(goat, no_std)]
: the return type is ()
it's not subject to RVO
besides I think it is safe to ignore parameters in most if not all calling conventions
since you never return, you never observe side effects of failed RVO
(I then posted a playground example which runs the transmute with Miri, and Miri complains that it's UB)
#![cfg_attr(goat, no_std)]
oh
so!
is not memory layout compatible with any type
Michael-F-Bryan: I believe it's because the general case of transmuting between functions that return different things is unsound unless the returned value is a pointer
which is why it's valid for compilers to apply things like RVO
Miri was happy for me to turn afn() -> *mut u8
into afn() -> mut String
Is my reasoning correct that, generally, optimisations like RVO being sound is what makes this transmute is UB?