-
Notifications
You must be signed in to change notification settings - Fork 61
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 u8into afn() -> mut String
Is my reasoning correct that, generally, optimisations like RVO being sound is what makes this transmute is UB?