[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)