It currently isn't possible to get a 'static dangling ref to a ZST without resorting to Box::leak or NonNull::dangling(), which is unsafe. While it does work for mutable slices, the produced assembly for &mut [] is less than ideal since it loads a pointer to the data section of the binary. (Compiler Explorer)
Also, the Box::leak method does not work without core::alloc.
A possible solution is std::mem::dangling, defined as:
This may need a different solution for str, since going from [u8] -> str with std::str::from_utf8_mut generates a lot of code. &mut str could impl From<&mut [u8; 0]>, for example.
The code appears to be a missing core::mem::forget(zst) (or equivalent). You can't copy the value with a Copy bound and as written it will be dropped at the end, which makes the accessible value afterwards an invalid copy. The intended effect is to pretend such a value to have beeen ''written-to'' the returned memory location. An alternative would indeed be ptr::write(_, zst) which is another no-op that simply forgets the value.
Creating a (reference to) an instance of an arbitrary ZST has to stay unsafe, because the ZST might be some kind of significant token (e.g. providing unique access to mutable static state). Just because a type has no data bytes doesn't mean you should get to construct it if the language doesn't already allow that.
I agree it can and should be possible in the case where you can construct an instance, though. (I just recently was disappointed to find out that I couldn't declare a const that was a mutable reference to a function item ZST, in order to make an easy "null value" of type &mut dyn FnMut().)
However, one of the author's goals is for this operation to be available in no-std and without mention an allocator in the type system. The static code path is indeed pretty similar to the implementation above except for the drop omission mention in my other comment. It doesn't even allocate.
I don't think we'll get a runtime-checked version in std, but if/when it's possible to write where const { size_of::<T> == 0 } (or equivalent), then it seems reasonable to add.
I would want to put the ZST requirement in the signature such that it can be made to always succeed, rather than panicking for non-ZST. Box::leak works fine for all types; the point of a new way of doing it is that it can work without alloc, but only for ZST.
I don't think we have any publicly exposed and stable functionality which panics based on ZST or non-ZST (though I could just be forgetting some); rather, there's a significant amount of effort to ensure everything doesn't break for ZST. The closest is the unstable slice::array_chunks which panics if the const N is 0, but there's documented intent to make that into a compile error before stabilization. To me, the static information of ZST-ness falls into that same category.
I don't think array_chunks should be blocked on that either.
The Rust standard library has plenty of APIs that document that they can panic if called with invalid arguments, because the type system can't easily express the constraint. In this case (and the array chunks case), it seems like a much more trivial burden to put on users to ensure their programs don't panic than in many others, such as bounds checking. I don't think useful APIs (here I am thinking more of array_chunks than this API) should be blocked on an language feature that hasn't even been properly designed yet unless the lack of that feature has a clear, major negative impact on usability.