[HN Gopher] Methods for Array Initialization in Rust (2018)
       ___________________________________________________________________
        
       Methods for Array Initialization in Rust (2018)
        
       Author : danny00
       Score  : 92 points
       Date   : 2021-05-24 12:32 UTC (10 hours ago)
        
 (HTM) web link (www.joshmcguigan.com)
 (TXT) w3m dump (www.joshmcguigan.com)
        
       | rwmj wrote:
       | What's the underlying reason for arrays <= 32 elements to be
       | treated differently?
        
         | chrismorgan wrote:
         | It's a historical limitation, with the power of lifting it only
         | very recently stabilised (in the last couple of releases). Most
         | things that were limited to 32 when this article was written
         | aren't any more, though the Default trait implementation still
         | is.
         | 
         | 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.
        
           | masklinn wrote:
           | > 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.
        
             | [deleted]
        
             | swsieber wrote:
             | There have been various variable arity generic discussions
             | on the internals forum (internals.rust-lang.org), but I
             | think it's a significantly lower priority issue than the
             | other stuff in flight. I think we might get it in a few
             | years.
        
             | Diggsey wrote:
             | Why not? Rust isn't limited to what Haskell does, and a lot
             | of it's features are driven by a desire for parity with
             | C++, which does have variadic generics.
             | 
             | Other languages in the same domain (like D) also have
             | variadic generics.
        
               | masklinn wrote:
               | Because Rust's generics are patterned after Haskell's
               | (traits are dual to type classes), not C++' templates.
        
         | MauranKilom wrote:
         | Really stuck out to me too. Does that mean that somewhere in
         | there there are 32 different "overloads" (or whatever the right
         | term is) of this functionality for 1 to 32 elements? It feels
         | so out of place...
        
           | kzrdude wrote:
           | Formal systems are rarely perfect :)
        
           | nynx wrote:
           | It's not the case anymore.
        
             | MauranKilom wrote:
             | Ok, but why was it the case?
        
               | ekidd wrote:
               | Rust has two low-level "array" types:
               | 
               | - &[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.
        
               | rwmj wrote:
               | Thanks, I think this is the clearest explanation in the
               | thread.
        
           | tialaramex wrote:
           | Yes. This is untidy but effective.
           | 
           | 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.
        
             | estebank wrote:
             | > 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...
        
               | tialaramex wrote:
               | My error. Good point about the orphan rule. Because I was
               | writing about Tuples what I was talking about is actually
               | impossible in your own code I think, because tuples are
               | inherently foreign. Even though the code in std _looks_
               | tame, you are forbidden from doing this elsewhere. Even
               | for a hypothetical local type MyType you are forbidden
               | from attempting:
               | 
               | 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.
        
           | masklinn wrote:
           | Without const generics, traits had to be implemented on every
           | arity. Even with macros, there has to be a limit somewhere in
           | the number of impls you allow as the implementations are
           | explicit and eager.
           | 
           | 32 was the limit selected.
        
           | hderms wrote:
           | It actually isn't really that unusual to have things like
           | this in a statically typed language. Scala and Haskell have
           | similar limitations with tuples
        
           | simias wrote:
           | It was normally implemented with macros to hide the
           | boilerplate, but I agree that it wasn't very elegant. You can
           | understand why many of us were very happy to finally get
           | const generics...
        
         | rusbus wrote:
         | Traits are applied to types and because the length of the array
         | is part of the type, different length arrays are different
         | types.
         | 
         | 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.
        
         | qw3rty01 wrote:
         | The case in the article is for when `T: Default`.
         | 
         | 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`.
        
       | chrismorgan wrote:
       | (2018)
       | 
       | 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.)
        
       | m-ou-se wrote:
       | An important one is missing from this overview: using a const.
       | This is sometimes useful, because it does _not_ require the type
       | to be Copy; only that the value is a constant:
       | 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];
        
         | lmkg wrote:
         | Are empty vectors const as well?
        
           | masklinn wrote:
           | Vec::new is const (since 1.39), so yes.
        
         | oconnor663 wrote:
         | Oh wow, that's a big improvement. When did that stabilize?
        
           | m-ou-se wrote:
           | It was stabilized in 2019 by accident:
           | 
           | https://twitter.com/m_ou_se/status/1396827152859942921
           | 
           | https://github.com/rust-lang/rust/pull/79270
        
             | [deleted]
        
       | CryZe wrote:
       | There's also an open Pull Request for adding std::array::from_fn
       | which calls the function / closure provided N times to initialize
       | an array. This basically brings the array-init functionality into
       | std.
       | 
       | https://github.com/rust-lang/rust/pull/75644
        
         | skohan wrote:
         | I wrote a graphics library in Swift that uses a method like
         | this to initialize buffers and it's super useful
        
       ___________________________________________________________________
       (page generated 2021-05-24 23:01 UTC)