[HN Gopher] An O(1) Generic Blog Post About Rust
___________________________________________________________________
An O(1) Generic Blog Post About Rust
Author : peterkos
Score : 59 points
Date : 2022-05-02 18:30 UTC (2 days ago)
(HTM) web link (peterkos.me)
(TXT) w3m dump (peterkos.me)
| MauranKilom wrote:
| From the naive lens of someone writing C++, this all looks...
| extremely elementary. The first half of the blog post touts
| monomorphization, which (to my otherwise uneducated eyes) is
| nothing other than what template instantiation does in C++, no?
| And the second half is dedicated to the technicalities of why
| it's hard to make the compiler support the equivalent of the
| following C++ signature: template<int N>
| std::array<int, N+1> foo();
|
| I suppose there's some unmentioned "we don't want Rust generics
| to be Turing complete" in the deeper reasons, but... It sure
| makes me feel like either I'm taking a lot of things for granted
| in C++ or Rust painted itself into a strange corner.
|
| Am I missing something that makes Rust generics way more
| complicated than C++ templates? Or is retrofitting them into Rust
| really such a hellish adventure?
| Salamander9384 wrote:
| I think that this is a great example of some of the tradeoffs
| between Rust and C++!
|
| The main difference is that the C++ code will check everything
| at instantiation time, and C++ templates can lead to nasty
| error messages because it's hard to assign _blame_ to either
| the caller or the callee.
|
| As an example, consider the following code:
| template<int N> std::array<int, N> foo() {
| std::array<int, (N / 2) * 2> out; return out;
| }
|
| This code will only typecheck if N is even. But that's not
| written down anywhere, and if I tried to pass in an odd N, then
| I'd get some template error telling me that _something_ went
| wrong, and it may even give me a trace. But that error doesn't
| tell me whether it's a bug in foo, or a bug in how I'm using
| foo. If it's a bug in using foo, it certainly doesn't tell me
| how to fix things.
|
| Rust takes the approach of requiring that you specify
| constraints on generic parameters up front. It then checks
| whether the function's body will compile _for all_ possible
| generic parameters, given the constraints. This is much easier
| to use from a usability perspective, since you know whether to
| blame the caller or the callee, and it explicitly enumerates
| the requirements that you need to meet (i.e. the constraints on
| the generic parameter) in order to get your code to compile.
| This is why C++ lets you do many more crazy things with
| templates than Rust currently allows you to with generics (and
| why is moving more slowly to add things to its type system).
|
| C++'s alternative approach gives up usability for extra
| flexibility. Neither approach is "right." They're just
| different trade-offs.
|
| (As an aside, it turns out that the Rust type system was Turing
| complete even before const generics were added.)
| MauranKilom wrote:
| Thank you for the explanation, that makes it clear!
| saurabh-kushwah wrote:
| Similar to constant generics, any idea what `~const` means ?
|
| I can see some references in the source code, https://doc.rust-
| lang.org/src/core/result.rs.html#2057
| conradludgate wrote:
| To elaborate on what CUViper said,
|
| const functions are really 'maybe const' functions since they
| can be called with runtime arguments or compile time arguments.
|
| If you want your const function to depend on some trait, for it
| to even be possible at compile time, those traits would need to
| be possible to use at compile time too.
|
| But the same idea holds as arguments, we might just want to use
| it at runtime too, in which case it needs to be a 'maybe const'
| bound for the const function to be flexible enough for us.
| CUViper wrote:
| That's experimental syntax that means something like "maybe
| const" -- https://github.com/rust-lang/rust/issues/67792
| NoraCodes wrote:
| I appreciate the attention to the social and organizational
| aspect of implementation here. Every piece of software has a
| somewhat nonlinear curve in the difficulty of adding new features
| as the codebase gets more complex, but it's especially bad in
| compilers. Adding something as complex and all-pervasive as
| dependent types - and this is dependent types, just in a Rusty
| costume! - is, clearly, on another level. I'm very excited for a
| more capable incarnation of this feature, but I'm not holding my
| breath for its stabilization.
| davidhyde wrote:
| Great article! I would just like to add to the point about C#
| generics.
|
| > "generics in Rust are zero-cost abstractions. rustc performs a
| process called monomorphization, where generic items (methods,
| structs, etc.) are "flattened" at compile time into only the
| types that are used. (Compare this to a language like C#, where
| generics are evaluated at runtime.)"
|
| I believe that generics are evaluated at runtime in C# due to
| just-in-time compilation. C#'s implementation of generics (which
| they call reification) is more similar to Rust's monomorphization
| than, say Go and Java's lookup table type erasure approach.
|
| More information about this can be found here:
| https://stackoverflow.com/questions/31876372/what-is-reifica...
| Diggsey wrote:
| I think this is only true in C# for value types - I think the
| same generated code is used for all reference types, so eg.
| `List<object>` uses the same code as `List<string>` but
| different from `List<int>`.
| jhgb wrote:
| This is to be expected with what are essentially pointers to
| heap objects. The actual code being executed only manipulates
| machine-word-sized values with the same semantics, so it
| makes little sense to have copies of the same code.
| merb wrote:
| well yeah. it's explained here:
| https://docs.microsoft.com/en-
| us/dotnet/csharp/programming-g...
|
| btw. it's not called "runtime evaluation" and more or less a
| kind of runtime substitution. (because nothing gets evaluated
| it gets substituted)
|
| and btw. c++ allows references types to have specializations,
| this is not possible in c#
| geodel wrote:
| Go's generic are not type erasure.
| whateveracct wrote:
| `reflection` was updated to capture type parameters?
| melony wrote:
| What about the Dyn type parameter?
| davidhyde wrote:
| Good point! Using dyn Trait syntax (aka, dynamic dispatch)
| uses type erasure and is kind-of like Go and Java's
| implementation of Generics I guess. You can definitely use
| that in Rust but the article is about impl Trait syntax which
| is for concrete types (not type erased)
| estebank wrote:
| The only nitpick I'd have is that `impl Trait` doesn't
| necessarily _imply_ a concrete type. Or rather it does, but
| in so far `Box <dyn Trait>` is also a concrete type :)
|
| That means that you can have a function that returns `->
| impl Trait` with a concrete implementation because it has a
| single return value, but if you add a second and don't want
| to create an enum for a simple version of static dispatch,
| you can change the returned values to be trait objects and
| the callers don't break.
| kzrdude wrote:
| > And before that, in the pre-alpha days of Rust, arrays were
| defined with a variadic macro. The /* something */ above was a
| [T, ..$N], where T is the type, and ..$N defines a range (I
| believe -- old Rust is weird) up to the number of specified
| elements.
|
| This is just a misunderstanding. The `..$N` was just the array
| length syntax. Now the syntax is `[T; N]` with no change in
| meaning - there was no variadic magic.
| moonchild wrote:
| > And of course, this leads to any further optimizations the
| compiler may decide to do, re: inlining.
|
| Ah, so just like languages with 'runtime' generics like c# and
| java!
___________________________________________________________________
(page generated 2022-05-04 23:01 UTC)