You can collect into boxed slice. Though I do not think that for zeroes this is the best idea, this playground shows that optimizer does not use alloc_zeroed for core::iter::repeat(0).take(len).collect(), but does for vec![0; len].into_boxed_slice().
Interesting. There is no special case for alloc_zeroed directly in the macro, but if you go down to what it calls you will find it: alloc::vec::from_elem then alloc::vec::spec_from_elem::SpecFromElem::from_elem, specifically its specialization for u8 has the special case. It is also present for i8 and for a bunch of types implementing alloc::vec::is_zero::IsZero (which includes both i8 and u8, byte types just have extra optimization for non-zero case). And I thought it is from some of the optimization passes.
You'll see that it's just a call to __rust_alloc_zeroed plus the error handling (for things like alloc_bytes(usize::MAX) -- anything more than isize::MAX bytes is always disallowed -- and the possibility of the allocator returning an error).
Or, for a more obvious demonstration that it's not adding overhead, try
so that the optimizer knows it doesn't need to worry about the "don't allocate for len == 0" case and the "what if the length overflows isize case", and you get the very simple https://rust.godbolt.org/z/rqse6Ezsj
alloc_bytes:
push rbx
movzx ebx, di
mov esi, 1
mov rdi, rbx
call qword ptr [rip + __rust_alloc_zeroed@GOTPCREL]
test rax, rax
je .LBB0_2
mov rdx, rbx
pop rbx
ret
.LBB0_2:
mov edi, 1
mov rsi, rbx
call qword ptr [rip + alloc::raw_vec::handle_error@GOTPCREL]