Methods for Array Initialization in Rust (2018)
joshmcguigan.com
joshmcguigan.com
Some parts of this article are likely to be out of date, though I haven’t looked in detail (I’m just going to bed). Most significantly, the recent const generics stabilisations has shifted the balance of power and may have made some alternatives nicer. I do, however, note that [T; N]’s Default implementation is still limited to N ≤ 32, for technical reasons that haven’t yet been overcome. (https://github.com/rust-lang/rust/pull/74254 is relevant info that I’ve found on a quick search—I think the trouble is that [T; N] normally requires T: Default, but [T; 0] doesn’t, and this can’t be sorted out with the minimum const generics that has shipped. An unfortunate case of designing oneself into a corner by doing things that made perfect sense at the time.)
const EMPTY: String = String::new();
let a: [String; 1000] = [EMPTY; 1000];
const NONE: Option<String> = None;
let b: [Option<String>; 1000] = [NONE; 1000];
const SEVEN: AtomicU32 = AtomicU32::new(7);
let c: [AtomicU32; 200] = [SEVEN; 200];The key term to search for and read up on is const generics.
This style of limitation is still present on tuples, which have implementations for common traits for up to 12-tuples. (See the bunch of implementations around https://doc.rust-lang.org/std/cmp/trait.PartialEq.html#impl-... as an example, comparing it with the single “PartialEq<[B; N]> for [A; N]” implementation for all arrays!) This limitation will probably be sorted out eventually, but it takes time to settle on a design that satisfies everyone and is implementable.
That sounds unlikely, it’s would require not just const generics but some sort of variable arity generics.
If Haskell doesn’t have it, I don’t really see Rust getting it.
Other languages in the same domain (like D) also have variadic generics.
Regardless, Rust is not "patterned" after a single other language. It's influenced by many langauges, so it makes no sense to say that one feature is just copied straight from a specific other language.
- &[Elem]: A "slice" of elements, represented as a pointer and a length.
- [Elem; N]. A fixed-size array where the number of elements is known at compile time. This might be used for 2D or 3D coordinates, for example.
Array slices have always supported arbitrary lengths. But arrays with fixed compile-time sizes are represented as entirely different types: it's an error to pass a [i32; 2] to a function that wants an [i32; 3]. The size of the fixed-size array is part of the type.
And until very recently, Rust's type system didn't support defining methods on [Elem; N] that were generic over N. This is a feature that tends to be added later in many languages that support generics, not just Rust.
32 was the limit selected.
Here's what's left (this was greatly reduced by const generics already, as another poster has explained):
https://doc.rust-lang.org/src/core/array/mod.rs.html#383
That macro implements Default, but only for arrays of size 1 to 32. It doesn't make any sense to talk about a Default array of size 0, the array is empty, there's no "default" content at all.
You can see the same verbosity for places where in some other languages you'd be able to just say all these things just work with any number of elements in a tuple, and Rust has chosen "up to 12" instead because it cannot express that.
https://doc.rust-lang.org/src/core/tuple.rs.html#100
The top half of that file is explaining to Rust's macro language how to do various things for an N-tuple, and then the bottom half call that macro for the 1 through 12 sizes.
If you really need a 16-tuple (unlikely, but...) you could copy paste the code to make one with the same features. This isn't compiler support, so you could just do it yourself.
There's one wrinkle in that strategy in that you might run afoul of the orphan rule[1][2] where you can't write:
impl ForeignTrait for ForeignType {}
because if we did it would be brittle and changes to an external crate could make your unchanged crate stop compiling. So you're restricted to either implementing your own trait or any trait on a type you declared. To "get around this" you can use the new-type pattern[2]: struct MyArray<T>([T; 33]);
impl ForeignTrait for ForeignType {}
[1]: https://github.com/Ixrec/rust-orphan-rules[2]: https://play.rust-lang.org/?version=stable&mode=debug&editio...
impl ForeignTrait for (MyType,u8)
The compiler will conclude that ForeignTrait is foreign, of course which is what we expected, but (MyType,u8) is also foreign even though MyType is local and so the orphan rule forbids this definition. Shame.
It doesn't make any sense to sidestep with new type because if you didn't want a tuple you could already define whatever type you wanted and this isn't an issue anyway, whereas if you do really want a tuple then a new type isn't a substitute.
It's only with the very recent launch of const generics (allowing constant values like numbers as generic type parameters) that it became possible to abstract over things like array length.
IIRC there is some stickiness about the default trait that is still blocking a const generic array initializer.
The reason this limitation exists is because it was decided that an array length of 0 should impl `Default` even if `T: !Default`, so `impl<T: Default, N: usize> Default for [T; N]` can't be used.
This may be possible in the future if specialization is stabilized, but for now we're stuck with that limit for `Default`.