Of the three options you presented as being potential results of putting forward arguments supporting my dislike of Rust, the third is interesting. I am quite sure that a vast majority of actual Rust programmers would consider addressing my concerns to be an active degradation of the language. Somewhat related is that I'm not particularly concerned with people (particularly Rust users) agreeing with me, nor do I think that would be a plausible result. So, that leaves the potential for being shown information that would "address [my] concern" as a potential result. So...
I have relatively strong opinions about quite a few areas that Rust, as a language and accompanying programming & tooling philosophy touch on, so I'll just do a few as examples:
1) I am strongly adverse to package managers (I didn't pick this example to get a rise out of you in particular) and their usage by programming languages. I am hostile toward the trend toward more and more dependencies in a given executable, which is only made worse by the industry adoption of languages that support and promote the "find a crate/get from NPM" attitude toward incorporation of dependencies. I don't know if there is evidence of a exponential explosion of transitive dependency in languages relying and building on a package manager ecosystem, but I wouldn't be surprised if it worked toward that point. I know that one does not have to use Cargo and the crate ecosystem, but it is a huge point of pride for the community and is THE idiomatic way to handle dependencies in Rust.
2) I have strong philosophical disagreements with ad-hoc polymorphism in general and with Rust's choice of pervasive reliance on its trait system all through the standard library and all examples of idiomatic code. At this point in development I don't even think there is a means to remove the ad-hoc polymorphism from Rust's implementation as a language. This point in particular I can not see any active Rust user being seen as an improvement of the language. Further, and although Rust does not have a definition of the language or a formalization of the type theory used by the language, I can not see a world where Rust adopts Haskell's position on the typeclass system being a convenient syntax sugar for a non-primitive and more explicit semantic form.
3) I am both practically and philosophically opposed to the usage/presence of 'ownership semantics' as a core part of the semantics of a programming language. Note, that I don't oppose the encoding of the commonly used meaning of 'ownership' at the type level via expressive types, as it can be an accurate description of the relationship between various data in a program. I do object to 'ownership' being the foundational semantic understanding of all programs and data used therein. There is a chance that Rust could incorporate a sophisticated type theory in a future release that relegates the current imposition of universal ownership semantics into a constrained area and allows for alternative semantics to be used in appropriate places, but I think it is nearly impossible to do and maintain validity for any prior programs.
So, do any of those three example look particularly appealing? I know you, only by reputation and seeing previous comments on HN, and know you are fairly involved in the development of Rust from several directions. Can you see Rust becoming a language with very limited ad-hoc polymorphism, a strong break away from the ownership semantics used today, and a language that does not place a package manager in a place of importance for idiomatic development?
Of those three examples the only one I can see anything being said that would alleviate my dislike is to just not use Cargo and build everything from scratch or find and download tarballs, which I would probably do and not complain if I had to use Rust. Thanks for your response being not particularly aggressive, I appreciate any time you gave to read this wall of text.
> I have strong philosophical disagreements with ad-hoc polymorphism in general
What disagreements are those, if you don't mind typing more? PL design is something I'm interested in but not particularly experienced with so more perspectives are always nice to hear.
> I can not see a world where Rust adopts Haskell's position on the typeclass system being a convenient syntax sugar for a non-primitive and more explicit semantic form.
As someone who isn't familiar with Haskell, do you mind elaborating on Haskell's syntax sugar and what it reduces to?
My problem with ad-polymorphism is primarily that it encourages more generic-ness in authoring code, which I am aware is often lauded as a positive characteristic of a given program. Taken to the extreme, there are whole communities of developers that pride themselves of writing the most generic code possible. I hold the opinion that all code should be clearly specified and particularized to processing the actual data required by the specification of the problem domain. I am certainly on the extreme end of the spectrum of generic to highly specified, but I genuinely feel that better code and the resulting programs come from a low level and data specific design ethos.
Additionally, and more relevant to Programming Language Theory, the entanglement between ad-hoc polymorphism implementations and the detention of a programming language are a huge mistake from my perspective. This is where Haskell becomes relevant, because although there are some differences (implicit self, orphan issues, etc) between Rust traits and Haskell’s typeclasses, they are an extremely similar mechanism for achieving ad-hoc polymorphism. At this point in its development, and due to the pervasiveness of usage throughout the stdlib, I see it as almost certain that Rust will attempt to kludge traits into a formal definition of its type theory and language definition making any attempts at coexisting with other language in a single application more difficult. Comparably, in Haskell, typeclasses are merely syntactic sugar allowing for a better developer experience using ad-hoc polymorphism. Specifically, Haskell, the language as defined, achieves ad-hoc polymorphism by passing a dictionary parameter to functions using overloaded function names. This is done using the standard terms in Haskell and has a clear denotaional semantics for such terms. Rust usage of traits is talked about and reasoned about as a more primitive part of the language, not just a pretty piece of sugar with a more basic existence in the formally defined core of a programming language.
If ad-hoc polymorphism is something a language designer wants to incorporate into the usage of a language, my position is that defining a typeclass style sugar over a clearly defined set of terms in the core, formally defined language would be at least a method of prohibiting issues with the resolution, definition, and various methods of implementation of ad-hoc polymorphism polluting the syntax and semantics of the language itself. But the above requires a firm definitional boundary between what is the language and what is syntactic sugar to be built into the definition of the language and built into the compiler infrastructure.
A more out of band way to achieve ad-hoc polymorphism would be to have a pre-processor that is a part of the language distribution and maintained by the language designer/org that does the resolution/solving of the ad-hoc polymorphism and then presents the compiler with source text with no overloading.
There are also type theoretic solutions to ad-hoc polymorphism l, but that’s a little outside the scope (even if it my personal solution to having and limiting ad-hoc polymorphism in a language).
Woah! Thank you for taking the time to explain your perspective and thoughts! It's a lot of food for thought. I wish I had the background and knowledge to discuss things on equal footing :(
Just a few additional questions/comments:
> Specifically, Haskell, the language as defined, achieves ad-hoc polymorphism by passing a dictionary parameter to functions using overloaded function names. This is done using the standard terms in Haskell and has a clear denotaional semantics for such terms. Rust usage of traits is talked about and reasoned about as a more primitive part of the language, not just a pretty piece of sugar with a more basic existence in the formally defined core of a programming language.
Would it be accurate to say that Swift's non-monomorphized generics are more along the lines of the Haskell approach you prefer (i.e., single function implementation with a separate parameter for additional type info)?
And a bit more hand-wavey, but IIRC Rust's generics were defined in such a way that monomorphization is technically an implementation detail; in other words, I want to say a Haskell-like approach isn't strictly ruled out. I'd take that with a large grain of salt, though.
> If ad-hoc polymorphism is something a language designer wants to incorporate into the usage of a language, my position is that defining a typeclass style sugar over a clearly defined set of terms in the core, formally defined language would be at least a method of prohibiting issues with the resolution, definition, and various methods of implementation of ad-hoc polymorphism polluting the syntax and semantics of the language itself. But the above requires a firm definitional boundary between what is the language and what is syntactic sugar to be built into the definition of the language and built into the compiler infrastructure.
Might MiniRust [0] be something along the lines of what you would desire? It seems to fit the general idea of a smaller formally-specified "core" language. There's also this bit about traits specificlaly from the readme:
> That translation [from Rust to MiniRust] does a lot of work; for example, traits and pattern matching are basically gone on the level of MiniRust.
Swift is a good reference point in this area because Swift essentially took the dictionary-passing approach of Haskell and added the ‘low level’ type information like bit-width, offsets, etc as a Type Metadata parameter. The big upside is that Swift gets a good deal of performance boost compared to Haskell and other languages that have uniform datatypes (boxed values). So to extend the concept I was describing from Haskell to Swift would be creating concrete syntax for the language, which has no semantic identity outside those already existing in the semantics of Witness Tables. Sometimes the term “derived form” is used to talk about syntax like this. It is the term for concrete syntactic forms that are only defined in terms of syntax forms in the base/core language. It is, for the most part, correct to view derived forms as macros or as a more general metaprogramming construct, but but in Haskell’s case the macros are implemented in the compiler and expanded in a particular phase of compilation.
Swift is also good touch point for considering what the compiler does with the actual implementation of ad-hoc polymorphism. Swift is optimizing to monomorphized instances of a generic function give some heuristics about expect performance gains by specializing the function for a given type (Haskell also does this in GHC, but still pays a performance price for using boxed values).
So to answer the question: the part of Haskell’s implementation of typeclasses that I think is the correct method is that it is merely a derived syntactic form that is expanded by the compiler into Haskell’s actual language (its abstract syntax as encoded as ghc-core in GHC in particular). From this perspective Swift doesn’t provide the derived form, it just provides the implementation directly for developers to use omitting the sugar that Haskell provides. I tend towards explicitness as a strong default in language design.
Rust doesn’t have a formal semantics currently, so they could certainly adopt a derived form approach to traits, but I don’t know enough Rust to be able to determine what issues existing thoughts, assumptions, and type theory ‘commitments’ would act as obstacles to such a semantic.
As to MiniRust, Ralf Jung (at ETH Zurich) has done some excellent work, along with some of his students (Max Vistrup’s recent paper on Logics a la Carte is very, very good). MiniRust does attempt to be comparable to Haskell’s GHC-core. So in the sense of being or approaching what I would view as the (excluding type theory based options) correct way to implement ad/hoc polymorphism, yes, to sum: MiniRust as the semantic definition of a derived syntactic form and a compilation phase for ‘expansion’ of the trait macro.
Those explanations aside, my issues with ad-hoc polymorphism do not go away under this implementation, I’m generally opposed to generic functions (especially function name overloading). But I think that if a language is pursuing ad-hoc polymorphism as a feature they should pursue it in a well founded and formal manner.
Both of the example things you picked are generic types, and container-esque types at that. I think that my opposition to generics in general is a scale of dislike for different uses of generics. So, an off the cuff scale from (well founded and acceptable in certain cases) to (a strict negative in nearly all cases) would be:
Polymorphic Types
Parametricly Polynorphic functions
‘Well Motivated’ Ad-hoc Polymorphism
Basic Function overloading
Basic ‘Operator Overloading’
Function overloading for symbols that are treated as ‘special’ by the compiler
I think the hashmap case is illustrative for my general perspective. I do see the value in being able to have polymorphism for function arguments which are generic in a type parameter. However, consider that the ideal/most performant hashing function for keys differs not just based on general types (int vs string) but can differ based on something like string length or bit width or signededness. My position is that a language should prioritize the ability to encode those exact requirements in a data structure and difficulties for achieving generic-ness be damned. Each function taking a hashmap as argument should be tied to the optimizations and low level considerations intended by the developer.
I am not opposed to some duplication of code where it produces the appropriate code for the problem being solved. My generalized dislike of ‘generics’ is there, but in my comment above I was mostly discussing ad-hoc polymorphism as a means of enforcing some general reasoning ability onto function name overloading. And as I implied in my scale above I find it particularly distasteful (basic function name overloading), if not actively harmful.
For generics there are two areas often conflated in conversation that I find to be wildly different in formalizations of type theories and languages: first, there is static phase type application associated with System F and higher order polymorphic lambda calculus more broadly. I obviously would like to see a more specific and limited implementation of generics at all levels of abstraction, but the higher the abstraction goes the more ‘sense’ generic-ness makes. Second, there is generics as a name for function name overloading, which is distinct from parametricly polymorphic function as well as distinct from generic types. I really dislike this usage of generics and do not think it is a good practice for developing quality software or for readability, maintainability, or efficient optimization. Obviously this is a scale as well. I would put Swift in the lead with Witness Table semantics for generics, then typeclasses and traits, then any less structured implementations at the bottom.
I agree there are significant costs to generics (especially readability), but there are large classes of problems that are a right pain without them. Even Go added them eventually.
This has nothing to do with Rust the language, other than the incidental fact that cargo happens to be bundled with Rust. There are no cargo-specific concepts whatsoever in the Rust language, just like there are no Cmake-specific concepts in C++. I know you alluded to this in your post; I just want to make sure it's crystal clear to everyone reading.
Consequently, people can, and do, use Rust without Cargo. That is obviously not what the majority does, because cargo is so easy to use with rust, but it is certainly possible both in theory and in practice.
If it's a "point of pride in the community" and "idiomatic" -- who cares? Rust is a general-purpose systems programming language. You can use it however you want, without listening to what people on Twitter think about it.
This particular complaint strikes me as social/cultural rather than technical -- "people who like Rust do something I find annoying, therefore I must conclude that they're not of my tribe, and reject rust". That is very understandable human nature, but not logically justified.
As for your other two complaints, you are correct that they are fundamental to the language and will never change for at least as long as the language is called "rust".
But since these complaints are totally subjective (a reasonable person might like or dislike them), it doesn't really seem fair to bitch about a language existing and becoming popular that has these properties.
For example, I complain about Go because I think it has actual defects that make it harder to use for almost any competent programmer. I also don't like Python, but I think the tradeoffs it chose to make are at least intelligible and some people prefer the Python style, so it would make no sense for me to complain that other people use it in their projects.
My reason for citing the package manager (and its popularity) as a reason I dislike Rust is because the language org that oversees the language oversees Cargo and its ecosystem. It’s not a tribal thing, it’s a “I feel like this choice enable the active pursuit of a societally/industry detrimental practice”. But I did and do concede that the package manager is not likely to be a part of an eventual formal definition of Rust.
Regarding that it might not be fair to ‘bitch’ about a language ‘existing and becoming popular’ over subjective disagreements: as I’ve said in other comments, and other places, I do not have any issue with developers using or enjoying Rust in their work, I’m all for people making software that is more economical in its usage of hardware and associated/entailed resources. I just don’t want Rust to be imported into existing codebases in other languages (and certainly not into software where it adds a non-copyleft dependency to a GPL’d core).
Working on adding that to the standard library, along with a handful of other "you should be able to do this without a dependency" things. We're being very cautious to not fall into the trap of "the standard library is where code goes to die", which is the problem some languages' standard libraries have had. But there are more things we should add, nonetheless.
From the outside looking in, it seems that despite trends in language development to the contrary, Rust has taken an extremely conservative stance on the inclusions into and the evolution of its standard library. I applaud the decision and the will of the core teams for sticking to their guns in this area. I had assumed that the reasons you give above were the motivating factors for being vigilant about the slower cadence of development and smaller ‘size’ of additions/alterations/removals to the standard library . But having it confirmed makes it very comfortable for me to think it is a nearly unqualified positive stance for the language to take.
I have seen your talk about the long future of Rust and I think there is the implication that you and the other Rust language developers are looking toward a 25-30 year timeframe (which encompasses nearly every language in wide spread use today) as the minimum expected lifespan of the language, with a view to be responsive and flexible enough to see the language continue to adapt and evolve throughout a longer period as well. With that type of longevity as an active consideration in planning the language’s growth/evolution I think Rust is setting a good precedent for non-BDFL style governance and stewardship of programming languages. I would hope a more long term compatible view takes hold in programming language development particularly, but general software development could probably stand to benefit if the time horizon was lengthened well beyond what seems to be standard today.
How is it any harder than using C? Sure, the C standard library gives you random numbers, but it doesn't, for example, give you any containers (hash maps, vectors etc.) which are needed even more often.
> Generate a random number.
Well, obviously, I would use cargo and pull in the `rand` package, because I'm not an anti-dependencies ideologue. Cargo makes it easier to depend on third-party code; that's the whole point. But the OP explicitly doesn't want that, so presumably he'd write his own RNG from scratch, or download a tarball of the `rand` package and build it manually without cargo. None of this is any harder in Rust than it would be in C or C++.
In (2), I'm surprised to hear your description of Rust's generics as "ad-hoc polymorphism". I tend to hear that term used to describe non-trait based systems, like C++ generics (in the absence of "concepts"), which act like a compile-time version of duck typing. I think of Rust's generics as trait-based polymorphism, not ad-hoc polymorphism, and I prefer the former over the latter. To a first approximation, I see Rust's generics system as somewhat related to Haskell's. I would be interested to better understand how you see the differences.
From the way you're describing it, it sounds like you might not fundamentally object to generics in general, just to the idea of doing it through monomorphization rather than "dyn Trait"-style vtables? If so, then that's understandable. I like that Rust has both options, but I can absolutely appreciate preferring to do everything via vtables, which has both advantages (code size, one kind of conceptual simplicity) and disadvantages (more difficult to do per-implementation optimization, without doing devirtualization which amounts to monomorphization anyway).
We do have some forms of first-class "type functions" or "type constructors" or "rank-2 polymorphism", in the style of Haskell, so that you can be generic over something like `Vec`. It's a little indirect at the moment, going through traits with associated types, and I'd love to see a more direct version where you can literally write `Something<HashMap>` or `Something<BTreeMap>` and have those be type functions of one parameter (Haskell "kind" star -> star).
In any case, we have talked many many times about the idea of having more global options to say "don't monomorphize by default, do everything with trait objects". We also are likely to build a stable ABI atop trait objects and vtables eventually, so that it's possible to pass around pointers to data structures and their associated functions, without relying on the layout of the structure.
For (1), I do think we need some mechanism to integrate dependencies. I can appreciate the aversion to the default of "get things from the network". We do try to have good support for vendoring and similar, and this is critical for many projects (e.g. Rust in the Linux kernel, or Rust in CPython, are not going to allow downloading dependencies from the network). All that said, there is a tradeoff between "one first-class package manager and build system" and "lackluster support for various build systems", and Rust picked the former deliberately. We do like being able to implement things in rustc and plumb them through Cargo, so that they're available through the whole stack when that's necessary to make a feature useful. But we also have many users who need to use rustc without cargo, and who ensure that rustc features work with other build systems, notably large corporate monorepo build systems.
As for (3), fair enough, that is very much a philosophical difference between you and Rust, and Rust isn't likely to change that. I do think we're going to make ever more sophisticated ownership semantics in the future (e.g. making self-referential data structures possible), but I doubt Rust's model will stop fundamentally being based around ownership and borrowing.
I just wanted to drop a quick comment to clear up your first question. The term ad-hoc polymorphism to describe both Haskell and Rust’s typeclasses/traits is taken directly from Wadler and Blott’s paper which introduces the idea/concepts of type classes to Haskell. The name of that paper is ‘How to make Ad-Hoc Polymorphism less Ad-hoc’. This paper laid the groundwork for the implementation Rust uses and it is a mechanism for restraining ad-hoc polymorphism. But I think the term still applies to both Haskell and Rust’s typeclasses. Ad-hoc polymorphism is not a derisive term (when used as a term of art in discussions of implementations of programming languages), it is merely the PLT way of saying function name overloading. Rust and Haskell use very similar system to impose restrictions and semantic guiderails on ad-hoc polymorphism, but both languages still have it. To sum, I certainly do not mean any negative connotation with the term, I feel I am using it appropriately and as intended in this domain of discourse.
PS. I intend to write a more substantive reply to your comment, but didn’t think I should leave this unsaid.
I have relatively strong opinions about quite a few areas that Rust, as a language and accompanying programming & tooling philosophy touch on, so I'll just do a few as examples:
1) I am strongly adverse to package managers (I didn't pick this example to get a rise out of you in particular) and their usage by programming languages. I am hostile toward the trend toward more and more dependencies in a given executable, which is only made worse by the industry adoption of languages that support and promote the "find a crate/get from NPM" attitude toward incorporation of dependencies. I don't know if there is evidence of a exponential explosion of transitive dependency in languages relying and building on a package manager ecosystem, but I wouldn't be surprised if it worked toward that point. I know that one does not have to use Cargo and the crate ecosystem, but it is a huge point of pride for the community and is THE idiomatic way to handle dependencies in Rust.
2) I have strong philosophical disagreements with ad-hoc polymorphism in general and with Rust's choice of pervasive reliance on its trait system all through the standard library and all examples of idiomatic code. At this point in development I don't even think there is a means to remove the ad-hoc polymorphism from Rust's implementation as a language. This point in particular I can not see any active Rust user being seen as an improvement of the language. Further, and although Rust does not have a definition of the language or a formalization of the type theory used by the language, I can not see a world where Rust adopts Haskell's position on the typeclass system being a convenient syntax sugar for a non-primitive and more explicit semantic form.
3) I am both practically and philosophically opposed to the usage/presence of 'ownership semantics' as a core part of the semantics of a programming language. Note, that I don't oppose the encoding of the commonly used meaning of 'ownership' at the type level via expressive types, as it can be an accurate description of the relationship between various data in a program. I do object to 'ownership' being the foundational semantic understanding of all programs and data used therein. There is a chance that Rust could incorporate a sophisticated type theory in a future release that relegates the current imposition of universal ownership semantics into a constrained area and allows for alternative semantics to be used in appropriate places, but I think it is nearly impossible to do and maintain validity for any prior programs.
So, do any of those three example look particularly appealing? I know you, only by reputation and seeing previous comments on HN, and know you are fairly involved in the development of Rust from several directions. Can you see Rust becoming a language with very limited ad-hoc polymorphism, a strong break away from the ownership semantics used today, and a language that does not place a package manager in a place of importance for idiomatic development?
Of those three examples the only one I can see anything being said that would alleviate my dislike is to just not use Cargo and build everything from scratch or find and download tarballs, which I would probably do and not complain if I had to use Rust. Thanks for your response being not particularly aggressive, I appreciate any time you gave to read this wall of text.