Although the author talks about Rust, I interpreted that point as generic. Rust happens to have characteristics that the author likes, such as static typing and declared "exceptions" in the form of Results.
I agree that languages with invisible program effects are harmful. The author said it another way as "Inconvenient knowledge is better than convenient ignorance".
I've been bitten many times when using languages which have weak types and perform implicit type conversion. I much prefer to know exactly what type of variable I'm using and perform explicit type conversion instead of having the language "take care of it" for me.
I think you're comparing Rust to a language with ad-hoc polymorphism like C++ (where you pay for the somewhat simpler signatures with confusing template instantiation errors), and I'm very glad we didn't follow the C++ route. Rust's generics are very similar to those of Haskell.
The only major generics-related issue that is getting some significant thought is the ability to have the automatically derive the return type on top-level functions, which is not something that is common in other statically typed languages (only C++ and those with whole program type inference, which has its own set of drawbacks).
I personally have no issue with it. Because Rust's type inference is so good, you don't generally even have to specify generic type parameters for method calls. It is more verbose and perhaps you could say less elegant, but I think it better expresses the concept.
That's a good point, but I am not sure it would actually be a problem.
For one thing, we already have syntactic collisions that don't seem to cause much problem (consider `foo?.bar` in .ts vs .rs), and this one would probably be prevalent enough that it would quickly be familiar to anyone using the language.
For another, if we squint I'm not sure those other languages aren't "really" using it for the same thing. If in some module we define `type Result<T> = Option<T>` then we have the same behavior in Rust, and we can imagine that those other languages have basically done so implicitly, meaning it's a less flexible version of the same mechanism (put to slightly different purposes).
I think what author is saying is that Rust won't let you have any type-specific behaviors in generic code, on principle. Generic Rust code is meant to rely only on the trait bounds, and nothing else. The compiler will eventually know concrete types, but it won't let you depend on that.
This is in contrast with Rust macros and C++ templates where the template code can perform operations that work only on some concrete types and not others, without declaring that upfront with trait bounds.
In other words, Rust's generic code is type-checked at the definition time (while the type T is still a truly abstract concept), rather than type-checked at use/instantiation/monomorphisation time when the type T can be substituted for something specific.
This is mistaken. Rust has explicit type declarations, augmented with type inference. Furthermore, unlike popular statically-typed functional languages, Rust's type inference is deliberately restricted to only operate within functions (intraprocedural) rather than between functions (interprocedural), which means that one always has explicit types available in the function signature when reading code.
This seems like too narrow a reading of OP's point. Rust imposes not only types, but also a particular tree-like program structure which makes it awkward to express lists, upward references, and other constructs.
My point here was that this was equivalent to using Object type in Java/C#. Here the data race guarantees of rust type system are elided due to this GC.
Yes, Rust was designed from the ground-up to leverage generics, and as a result, the ecosystem is full of generics. There is overuse of generics in Rust.
> ...by design it does not have reflection / RTTI,..
I don't think of those as comparable features.
Reflection is more or less an alternative way to accomplish many of the same things that you get with macros in Rust. Where you'd see reflection in C# or Go is for something like serializing an object to JSON, and in Rust you'd get some library with a macro to do it for you. The Rust version could still be completely monomorphic, most of the time.
RTTI is just std::any. Rust has it. However, 99% of the time, when I'm using dynamic_cast<T>(x) in C++ or switch x := x.(type) in Go, I would have been using an enum in Rust.
The convenience overloads for Rust are nice, that's not the overuse I'm talking about. I'm talking about the fact that some library authors seem to avoid &dyn like it were poisonous, even if it makes total sense and would make large chunks of your library monomorphic. There are also minor code size issues that come about because some library authors forget that the convenience overload should probably just call an underlying monomorphic function, especially for cases like AsRef arguments.
I am under the assumption here that if you really like generics, you probably like Rust, and vice versa. This is kind of a taste thing, so what is overuse of generics for me (hello, C++'s std::chrono, std::mt19937) might be the bee's knees for you.
I did a fair bit of Haskell and C++ before Rust, and as I got more experience with those languages, I used generics less. Monomorphic types are always easier to understand, and they should be preferred.
Rust also has features which interact in subtle and surprising ways. Generics and operator traits bumping into the "coherence" rules is my personal pet peave. I doubt any user would have predicted that interaction, and even though the compiler gives a detailed pointer to why, it's cold comfort.
Honestly, after hating C++ for years, trying to use Rust made me appreciate more about C++. I wish someone would make a language which took the best parts of both.
Rust's generics don't have overhead and the error messages are at least as sane as Java's. I think the problem is with how C++ implemented its templates.
> Like, if I want to parse command-line arguments.
What command-line parsing libraries have “a bunch of unnecessary type parameters”?
Only library I can think of which even has them would be structopt, and that’s not unnecessary, the entire point of the library is to deserialise to a structured type.
And of course rust was designed from the ground up to leverage generics, e.g. by design it does not have reflection / RTTI, interactions with variable or foreign types are thus generics based (often, sometimes trait objects will do though they have their own tradeoffs), or it doesn’t have overloading so while not handing all overloading use cases by a long shot it uses conversion traits (aka generics) for “convenience” overloads e.g. foo(string) and foo(path) become foo<T>(_: T) where T: AsRef<Path>.
This isn't wrong, but it seems to be putting the cart before the horse: even with limitations, having generics is still an improvement over not having them, in terms of one's ability to build type-safe abstractions (i.e. Rust would be a far weaker language without them). For instance, one can write a complicated lock-free data structure that works with any type T, doing the effort to optimise the atomic orderings and avoid leaks (etc.), all without forcing dynamic type casts or unnecessary allocations on downstream users.
> The way the Rust compiler works it isn't possible without specifying various bounds on the traits.
This is an explicit choice: C++-style check-at-use-site results in some seriously ridiculous error messages, and there's no boundary between implementation and signature, meaning a change to some small detail (and even syntax, in C++) in the implementation can accidentally cause downstream code to fail to compile. It is definitely annoying some times, but Rust, and most other languages with some sort of templates/generics, thinks the benefits outweigh the cost.
> And there is no specialization, which is a severe limitation.
(0) Rust doesn't really have much support for abstract data types. What Rust has is something not too unlike Haskell: you can hide `struct` fields [though not `enum` constructors], but the `struct` remains concrete. With a bona fide abstract data type mechanism, as in ML, you can give a module a signature that reveals the existence, but not the representation, of its type components. I'm guessing this much representation hiding isn't fully compatible with Rust's goals, which require knowing the sizes of types statically, unless they're behind a layer of indirection.
(1) Abstract data types have no relation whatsoever to type inference. [Nor do algebraic data types for that matter.] An abstract data type is an existential type: a dependent pair consisting of a type `T`, and a value whose type is `F T`, for some type function `F`. The closest thing Rust has to existentials is trait objects, but these can't do everything that existentials can do.
> Knowing the type of a variable is not always helpful during normal reading (e.g. the types of iterators in C++ or Rust).
Not only that, but there are some types in Rust that you _can't_ even specify; closures specifically don't have concrete types that can be specified in a Rust program.
I agree that languages with invisible program effects are harmful. The author said it another way as "Inconvenient knowledge is better than convenient ignorance".
I've been bitten many times when using languages which have weak types and perform implicit type conversion. I much prefer to know exactly what type of variable I'm using and perform explicit type conversion instead of having the language "take care of it" for me.
reply