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