The core library will implement Default trait for normal array types, for example:
let array = <[i32;3]>::default();
But an array represented by const generic will not:
trait Foo: Default {}
trait Bar {
fn bar();
}
impl<T: Foo, const N: usize> Bar for [T; N] {
fn bar() {
let array: [T; N] = <[T; N]>::default();
}
}
Even I declare that trait Foo must implement Default, the compiler said:
error[E0599]: no function or associated item named `default` found for array `[T; N]` in the current scope
--> src/main.rs:9:39
|
9 | let array: [T; N] = <[T; N]>::default();
| ^^^^^^^ function or associated item not found in `[T; N]`
And it doesn't allow me to implement manually either:
You can't. And the standard library not providing such an implementation is a know limitation (which is basically due to the fact that [T; 0] has at some point or, probably accidentally, gotten a Default implementation without a T: Default bound).
You can still make your bar function work, using array::map
trait Foo: Default {}
trait Bar {
fn bar();
}
impl<T: Foo, const N: usize> Bar for [T; N] {
fn bar() {
let array: [T; N] = [(); N].map(|_| T::default());
}
}
I noticed that the core library implemented arrays which size is 32, do you know where is the 32 comes from? Is this the same reason because of the limitation you mentioned?
Back when those implementations were written in Rust 1.4.0, there was no such thing as const generics so there needed to be an explicit Default implementation for arrays of each size.
The std authors used a macro to automate the boilerplate, but because it isn't practical to generate an impl<T: Default> Default for [T; N] block for each N from 0 to infinity, they chose to stop at a "good enough" number... 32.
It looks to me that since const generic becomes stable, the std lib could update the implementation with const generic, and if so, my case will also be solved, right?
The standard library can't have both impl<T> Default for [T; 0] and impl<T: Default, const N: usize> Default [T; N] because one isn't an exact superset of the other (the condition for specialisation).
You also can't remove the [T; 0] impl because it will break the code of anyone using <[T; 0]>::default() where T: !Default. That also assumes removing such an impl is allowed... as I understand it, editions don't let you remove trait implementations because std types from one edition of Rust must be usable with code written against a different edition.
Seems like they might have painted themselves into a corner
trait Foo {}
fn default<T: Foo>() {
let a : [T;0] = <[T;0]>::default();
}
this will compile even though Foo is not a sub trait of default, but this will not:
fn default<T: Foo>() {
let a : [T;1] = <[T;1]>::default();
}
If I understand right, this feature does not use trait implementation, but somehow hard code in compilers, that why it conflict with impl Default for [T;N];, right?
I don't know whether it was deliberate or an accident, but it's definitely useful.
I think we need some (very limited!) way to specify impl priorities, at least for std, but I haven't found such good way until now.
The best solution I have is to mark an impl as #[trivial] or something when a #[trivial] impl can conflict with other impls; that will also solve the questions raised from time to time about clashing with impl<T> From<T> for T.
But it may have the same soundness hole as specialization, I haven't checked that.
There have been a few attempts, perhaps this is the most recent, but it's a tough nut to crack in a way that doesn't depend on questionable implementation, doesn't disrupt inference, doesn't make other teams balk, etc.
So I think focusing on letting N > 1 rather than using specialization is the right way to go, just don't know when will the grammar finally be landed...
Because you can create an empty array of absolutely any type whatsoever, out of thin air.
This works because creating an empty array doesn't involve creating any elements – in fact, creating an empty array basically doesn't involve… pretty much anything. It needs no memory to allocate, no values to move/copy, nothing.
(The only case I can imagine involving some actual work is when an empty array is coerced to a slice reference, in which case an appropriately aligned pointer must be created. But that's just a constant equal to align_of::<T>() anyway.)