I'm reading through the source for RawVec, and it seems odd that it's all following the same code path for ZSTs and types of other sizes. But for obvious reasons, it has no real alternative other than a bunch of repetitive if std::mem::size_of::<T>() != 0 { ... } else { ... } in every call (which it could do).
Specialization does bring many useful things to the table, and I could foresee this as one potential application, simplifying RawVec for that. There's also a few other cases where it might be useful, like Option<T> and Result<T, E> where std::mem::size_of::<T>() and std::mem::size_of::<E>() are both less than std::mem::size_of<isize>() - this lets you make that optimization without baking it directly into the compiler. And of course, one could imagine much more complicated cases than these.
Going even further, specialization of struct contents would be great. Like a specialization of std::thread::LocalKey for non-Drop types so that they don't contain two unused bools.
You could also do it with mutually exclusive impls based on the value of an associated constant.
Then what you could write it as:
trait Size:Sized{
const SIZE:usize=std::mem::size_of::<Self>();
const IS_ZST:bool=Self::SIZE==0;
}
impl<This> Size for This{}
impl<T, A: Alloc> RawVec<T, A>
where
T:Size<IS_ZST=true>
{
fn allocate_in(capacity: usize, zeroed: bool, mut a: A) -> Self { ... }
}
impl<T, A: Alloc> RawVec<T, A>
where
T:Size<IS_ZST=false>
{
fn allocate_in(capacity: usize, zeroed: bool, mut a: A) -> Self { ... }
}
I don't see how specialization would make this any less repetitive,you would have to write the 2 impls based on the size of T anyway.
Can you expand on what you mean by this? Enum discriminants are already as small as they need to be(stored separately from their payload if the discriminant can't be stored in the payload).
I would consider that a complication, not a simplification. It transforms a local problem (code with branches) into a global one (having to look up which specialized impl is used for the particular type I am currently dealing with), which makes reasoning harder. One can't just accidentally ignore an if or match expression inside the function body, but it's easy to slip over the existence of a specialized impl (which, God forbid, might even be in a different module/file/directory!).
I agree with you on functions. But there are cases that control flow expressions are not enough, for example, what is the alternative to C++'s std::conditional<true, int, double>::type ? Is it possible to write type T = if const_val { i32 } else { f32 } ; in the future?
I hope not. I tend to be wary of wanting to pull C++ idioms and their supporting elements one-to-one into Rust. Rust has many advanced features (esp. in the type system) that C++ doesn't sport and which ultimately obviate the need for several helpers, hacks, and workarounds required in C++.
I must admit, though, that std::conditional seems powerful. (I'd probably still make a sound of discomfort upon encountering something like it in a Rust codebase.) What's the use case you have in mind for it?
As discussed in that thread, a possible workaround is using specialization (and in this example the never type, both also still unstable features). Adding a generic impl like the following works:
impl<True, False, const B: bool> Helper for ITE<True, False, {B}> {
default type T = !;
}
Because we have specialized impls for ITE<True, False, {true}> and ITE<True, False, {false}> the default impl is never actually used, and we can use this construct in generic contexts.
As for when you would want constructs like these; anytime you want to have a different layout depending on a const value. Toy example:
enum Encoding { UTF8, UTF16, UTF32 }
#[repr(transparent)]
struct CodeUnit<const E: Encoding> {
raw : ... // u8, u16 or u32 depending on the Encoding
}