And there it is. I programmed in C++ long enough to "know" this, but I still remember more jr. Engineers and even arguments with more sr. Engineers around this type of question.
Rust has removed this kind of question almost entirely from the language.
Personally, I have no intention of ever going back to C/C++ for any new project I work on.
"I invented the term Object-Oriented, and I can tell you I did not have C++ in mind" -- Alan KayHow do I knowwww????
$ g++ test.cpp
$ ./a.out
EDIT---answer removed to let others guess first.Sure, I see lots of evidence of junior developers living in a happy bubble where they apply language X to everything, when they could expand their horizons a bit by looking into Y and Z.
But this also ignores important factors such as:
* Convenience — if you've highly proficient in X, there's a low barrier to entry for anything you want to accomplish.
* Private ecosystem — if all your code is in X, you have potentially tons of reusable modules as part of your own stack.
* Public ecosystem — lots of libraries and community support.
* Mature documentation
* Stability
* Ease of hiring
and so on.
Also, not least: The journey of every developer is to broaden one's horizons gradually. As a junior you'll likely slog through a few fad languages until you reach a point where every technology out there, no matter how advanced, will suddenly become approachable.
The Blub idea also ignores the fact people can accomplish great things with poor tools. Choosing a minimal tool does not necessarily say anything about you as a developer. Sticking to one tool for many years probably says a lot about your ability to develop your skills, of course.
In short, I think Blub is a red herring, and I think it's a condescending one, a product of survivorship bias — you're not superior because you succeeded using a certain set of tools. You're probably successful for other reasons (which may correlate with your ability to choose the right tools, or not).
Graham's observation that "[languages] dictate the way [developers] think about programs" is the more important lesson to draw from his essay, though it's not exactly a new idea.
Never ever in my OOP days have I been able to reuse a non-trivial function from a different codebase "just like that". This is because all my functions were impure - something encouraged by most mainstream languages. So I had to first rip out the context the function was living in and make it work again before being able to use it somewhere else. I wouldn't fix it in the original place though, so next time I'd want to reuse I'd have to repeat every step again.
Since I started using a pure language, reusing code actually became possible. I can rip out a function and place it in a new codebase without changing it a bit and it's gonna work just like that, no problem.
Getting back to the topic at hand, I would define the Blub paradox as "being unable to logically explain the benefits of something solving a problem you haven't realized is actually a problem yet". This may sound like trying to create problems out of nothing just so that they can be solved by "cool feature X", but I'd guess in most situations this isn't the case.
I truly believed that I was creating reusable OOP components until I realized that actually I wasn't. I truly believed a normal editor is more than sufficient for my needs until I learned vim. I truly believed svn was the best thing ever until I learned git. Etc...
This has nothing to do with OO, really. But I don't disagree with your point about immutability.
In fact, I think the original essay makes quite some unwarranted assumptions:
But if you work for a startup that doesn't have pointy-haired bosses yet, you can, like we did, turn the Blub paradox to your advantage: you can use technology that your competitors, glued immovably to the median language, will never be able to match.
The median language will probably have a very good ecosystem that allows a small team of developers to quickly leverage. (Of course, someone who is not stuck in a Blub language has the same opportunity.) I think that there are very many counter-examples to the 'blub paradox'. Facebook was written in PHP, Paypal was written in Java, Dropbox was written in Python (which was a Blub language by 2007).
Graham's observation that "[languages] dictate the way [developers] think about programs" is the more important lesson to draw from his essay, though it's not exactly a new idea.
Indeed, this is a very important lesson, but not foreign to anyone who learnt LISP in the 70ies or Prolog in the 80ies ;).
Someone once claimed — I forget who and where — that Go was designed at Google precisely so that even average developers could contribute meaningfully and safely within Google's stack.
Of Google's top languages (as far as I know), Java and C++ are complex, and Python has its own idiosynchracies, notable the lack of static typing which leads to a class of potential errors (especially as relates to backwards compatibility between modules) that must be mitigated with super-extensive unit test coverage.
Go, on the other hand, is a very small and simple (easy to ramp up), very strict (hard to screw up) language with an extensive library (max productivity).
A junior dev might use a single language because they apply what they know and don't go outside their comfort zone. A senior dev might use a single language for every other reason.
But this is all from my perspective in a small startup where people have very broad responsibilities. I can completely understand a very senior developer working in a specific field (usually it's the low level stuff with C etc) who only works in one language because there's just no opportunity to use anything else.
Or perhaps someone who just sees their job as a 9-5 thing and looks elsewhere in their life for learning and growth.
Rust has lifetime elision for the common cases, though. It's unclear to me if the criticism was based on a pre-elision version of Rust.
In some talk, Andrei Alexandrescu said D is pursuing GC removal.
It'll be interesting to see if D and C++ with lifetime annotations can achieve useful results with less syntax for the cases where Rust's elision doen't work and you need explicit lifetime annotations in Rust. I have doubts, but of course both Alexandrescu and Sutter are working on Rust's competitors, so one would expect them not to say that Rust's more awesome than their languages.
As far as I can tell, this was an incorrect claim. The ISO Core C++ lifetime elision rules aren't meaningfully more aggressive than Rust's. We could easily add more elision to Rust if it turned out to be necessary, but the cases in which ISO Core C++ has extra lifetime elision rules that Rust doesn't don't come up often enough to make a difference.
Lifetime elision is a double-edged sword anyway. It's somewhat controversial in the Rust community, because it's not reading the lifetime annotations that causes the cognitive overhead: it's the semantics and what the compiler will enforce. Having fewer lifetime annotations can actually make the code a lot more confusing. Based on experience, I would caution C++ to not go overboard with it: being able to show pretty code on slides is not worth confused and frustrated users.
We added a lint to clippy that detects places where you could rely on elision but don't. I'm particularly fond of it, because many Rust programmers (including me) still default to the pre-input-output rule regime by explicitly typing out `fn<'a>(&'a u8)-> &'a u8`. It's caught a lot of this and made code nice and clean.
However, we've have multiple people ask the lint to be off-by-default for input-output cases where one of the two sides has a struct/enum lifetime (`Foo<'a>`, not `&'a _`), because it's nice for that to be explicit. (At the same time, multiple people feel like it should stay)
We'll have to see what happens when we RfC the clippy defaults.
That doesn't make them bad, just different! I welcome any effort to make C++ more safe, there's a lot of code out there that could benefit.
Even Go's error handling clearly isn't unusable, as controversial as it is, and try! is pretty much just a more sugary version of it.
Also, we were well aware of monads when we designed the Rust error handling system and in particular why they do not work very well in languages that have rich, imperative control flow structures. try! is basically just monads for imperative languages. To see this, work through what happens if you try to add break/continue/return to Haskell's system.
I would be very interested in a blog post on this.
When the blog post says "you don't need to learn monads", it's being tongue-in-cheek. He's pointing out that whilst Rust errors are monadic, you don't need to learn monads to work with them.
To be fair to Haskell, it doesn't force you to learn what a monad is either (ha!), but often the full details of what a monad is are emphasized before teaching IO or error handling. Which works somewhat, but also gives monads their infamy.
Additionally, the C++ example doesn't produce the same output ("Value: (x: 7)" and "Value: (x: 5, y: 10)"). The addition of that goal might lead the programmer to treat the C++ version of print_me as something more abstract, accepting a string and having class Foo and class Bar simply return their own string representations.
I acknowledge that the premise was "a beginner C++ developer", getting hyped up about inheritance and using it as the only tool in their toolbelt, but how many people are learning C++ as a first language these days? It used to be the norm, back in the OO-will-save-us-all heydey, but has anyone run into it lately?
edit: It's worth the down votes on this. Multiple inheritance is pure evil and confusion. In fact, even in Java I now rely on aggregation over inheritance.
http://stackoverflow.com/questions/269496/inheritance-vs-agg...
That said, yeah, the less class inheritance, the better. Ideally, work in a language where interfaces can include default implementations.
The Andrei guy says that Rust places too much emphasis on "clerical" memory management. This is not like a person who learned PHP from a couple w3schools article deciding that Lisp is "weird". The criticism is not that Rust is "weird" or somehow unintelligible, it's a direct critique of the language designers' choices.
This is the danger of using rules like the Blub paradox, you have to be careful or everyone who doesn't agree with you or doesn't like what you like is a Blub-programming dullard.
I actually do think there is something to the paradox, I would even say I have occupied different parts of that "ladder" myself. (That is, I'd like to think I'm higher on it now than I used to be.) But maybe JS programmers aren't interested in type-checking in their JS precisely because they prefer JS for its dynamically-typed nature. This is as opposed to someone coming to JS from a different language and bringing their preference for strong typing with them.
Then the author goes on to talk about a bunch of stuff that does nothing to explain Rust's apparent emphasis on memory. It's pretty much just a list of why Rust is cooler and better than C++. That's fine but none of it addresses the point made at the outset.
It might be a direct critique, but it's a critique of an incomplete picture of Rust: the rules that allow Rust to avoid a GC offer benefits far beyond just that, such as being a core component of Rust's concurrency story[1], and avoiding problems such as iterator invalidation (not generally a memory safety problem in a GC'd language, but still a semantic one, e.g. Java's ConcurrentModificationException).
(The "Weird feature #3" part of the article is exactly this point, although I think it doesn't go far enough in calling out the incomplete picture of Rust implied by the original quote.)
[1]: http://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.ht...
Sort of. Don't cite me.
This is only partially true. If you hold a mutable reference to a field in a structure, you can't get a reference to the entire structure. (Think of a mutable reference as a read-write lock, because that's what it is, just at compile time / type level instead of at runtime.) However, you can get a mutable reference to other fields in the structure. And an immutable reference to a field (analogous to a read-only / shared lock) does not prevent other immutable references, so you can totally get a reference to the structure while a reference to one of its fields is outstanding.
Here's an example of this on play.rust-lang.org: http://is.gd/ySSsex
That compiles and runs with the extra scope around the mutable borrows. If you comment out that scope, then yes, it won't compile.
My experience, coming to Rust from C++ and C, is that a lot of real-world C++ and C code is imprecise about mutability and shared references, and relies on the programmer not doing anything particularly weird. It's true that in this example, nothing would go wrong by omitting the braces because the references g and h aren't actually used. But in larger and legacy codebases, it's very easy to forget the mutability rules that were in the head of the previous programmer. So directly porting C++ code is going to be annoying, but that's mostly because the hard work is figuring out what was implicit in the C++.
> seemingly arbitrary rules
Most of them are about Rust's core guarantee: data race freedom. Some of them are due to a certain conservativeness of any static analysis, and may be relaxed in the future. > for the love of all that is holy, prove me wrong
It depends on exactly what you're doing. There are always ways to get around things, but it can depend on knowing Rust and its standard libraries well. As a younger language, some patterns are still being developed, and aren't always as obvious as they could be. We'll get there...Rust is certainly a different language, and if you try to port C++ code directly over, you may have problems. Such is life. :)
IMO this is just a part of it (and I think you agree, based on previous conversations). The actual thing is that the rules enforce a discipline about data, similar to the discipline in functional languages (except here it's allowing sharing XOR mutation instead of forbidding mutation entirely). This discipline gets us many things -- memory safety, safety from iterator invalidation-y things (there's a whole class of memory safety bugs that happen when you modify the exterior of a things whilst holding a pointer to the interior -- from iterator invalidation to invalidating pointers to a vector after truncation to invalidating enums), and clarity in code. Whilst the chronology of it's design may not be such, I personally look at data race freedom as something we got for free from this discipline, instead of the core focus of it.
I think conservative approach to fundamentals is a great way to build a robust language and the way it's presented to me suggests that's one of the objectives; however, for an outsider with experience in other, more lenient (and, obviously, bug-prone) languages, those constraints might appear too restrictive. I believe it's a transitional feeling though, hence calling them 'seemingly' arbitrary.
It's a really bad idea to try and write stuff in a "C++ way" with tons of mutation, Rust encourages a rather different discipline in handling data which is at odds with the regular C++ style of programming. But this takes time to pick up. Once you've programmed with it for a while it feels pretty natural, though, especially since it's very easy to reason about data in this model.
Here's why Rust has this model: http://manishearth.github.io/blog/2015/05/17/the-problem-wit...
As geofft mentioned below, you can get mutable references to multiple fields if you want.
In more complex situations, use Cell<T> (for copyable types, this is zero cost though it can prevent some optimizations) or RefCell<T> (this works for any type, but has a slight cost) for more fine-grained mutability control. You shouldn't need these often, but they exist
Ha! This is ironic because a present discussion over some syntactic sugar (maybe more) to make Rust error handling feel more part of the language and more ergonamic may be ad-hoc monads. Not that the programmer needs to know that - it's just something that should happen to play nice in the rest of the Rust type ecosystem
PHILOSOPHY MASTER: Most clearly.
MONSIEUR JOURDAIN: Well, what do you know about that! These forty years now I’ve been using monads in programming without knowing it!
-------------------------------
With many apologies to Molière, many things are monadic including state and exceptions. Most programming languages introduce them as first-class core concepts rather than as monad instances. Hence you can use them innocently, without realising that they are monadic.Part of being a Blub programmer is that you don't even think about the issues. In regards to the weird features brought up, the underlying issues behind these features have been in the C++ consciousness for some time. Below are some talks and resources covering at least some of these issues.
Sean Parent - Inheritance is the Base Class of Evil - https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&c...
Herb Sutter - You don't know const and mutable - http://herbsutter.com/2013/01/01/video-you-dont-know-const-a...
Andrei Alexandrescu - Systematic error handling in C++ - https://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012...
Herb Sutter - Writing Good C++ by Default - https://www.youtube.com/watch?v=hEx5DNLWGgA
Andrew Sutton - Generic Programming with Concepts Lite - https://www.youtube.com/watch?v=qwXq5MqY2ZA
One of the things that helps with avoiding "Blubness" in C++ is the significant interest in languages such as Haskel (which is not considered a Blub language by anyone except Agda programmers) and in applying the techniques and insights where possible to C++ whether through writing new libraries (see for example Fit by Paul Fultz II -https://github.com/pfultz2/Fit) or through new language features (see this post by David Sankel - http://davidsankel.com/uncategorized/c-language-support-for-...)
Finally, is also interesting that Paul Graham's blub paradox is being brought up in regards to Rust, C++, and D. The Paul Graham article is in large part talking about the power of meta-programming. In this regard, D and C++ are significantly more powerful in that regard. Features like higher kinded types (template templates), variadics, and non-type template parameters help in this matter. When you can write a function that can generically work with a tuple with any number and type of parameters, you can do some pretty neat stuff. For some examples take a look at Boost.Hana by Louis Dionne (http://boostorg.github.io/hana/). In this regards, Rust is actually the Blub compared to D and C++.
C++ and D programmers, like you (and Andrei), look down on Rust generics and give examples of all the things that you can't do with Rust generics that are easy to do with C++ templates. But there's an equally strong counterargument, in that Rust generics never give errors at template expansion time and are guaranteed to expand to valid code. This makes code easier to understand, improves the experience for users of your generics, and also simplifies the implementation, leading to potentially better compile times (since you typecheck once instead of after every template expansion). Of course, this is a tradeoff: it's more work for us to implement the features necessary to do the kinds of sophisticated metaprogramming you see in C++ and D, and there will always be some things you can't do in Rust that are easy in C++ and D. But that isn't a slam-dunk argument for untyped templates vs. typed generics any more than easy reflection is a slam-dunk argument for dynamic typing like Python vs. static typing like Java.
I think this demonstrates a significant weakness in Blub as a concept: there is not even a partial ordering of languages by power. The essay only works because Graham picks the two sides of the debate: Lisp, which Graham believes to be the most powerful of languages, and Blub, a theoretical language that can be stipulated to be less powerful than Lisp in all ways.
Outside the essay, "blub" only exists as a slur for a despised language or an insult for another programmer, and it's only useful as a way to start arguments.
> In this regards, Rust is actually the Blub
I'm not so sure. We are aware of the features that they have; we just prefer the strongly-typed versions to the stringly-typed[1] versions.We do desire more meta-programming features, and they will happen. Like all decisions, we don't want to rush into them.
1: This is not _entirely_ accurate, but I'm slightly at a loss for how to exactly characterize this at this particular moment. Rust's metaprogramming guarantees that you always generate valid Rust code, D's does not.
With regards to Rust, do you have any idea when more meta-programming features like variadics and non-type template parameters might be available in the language (are we looking at months, years, or decades)? Rust, has a lot of features that really interest me, but seeing the tuple serialization code brought back bad nightmares of simulating variadic templates using macros in C++ before I had a compiler that supported C++11 variadics. To me, at least personally, it felt like a step backward from C++14, and kind of curtailed my enthusiasm for doing a deep dive into the language. My test for the power of a language's type manipulation is how easy is it to write an implementation of apply(Function f, Tuple t) which will call f with the values of t. I would be most interested to see how Rust will implement this kind of metaprogramming.
(this could easily be a stupid question; I know I've come across as condescending to you before by mistake and if I've somehow done it again please believe that I meant to come across as genuinely curious :)
Seems this is the programming equivalent. Programmer X turns their nose up at my pet language Y. It can't be because of me, it has to be their naivitee/ignorance/lack of experience/bias, etc, etc.
That the article was a big "you're ignorant" to Andrei Alexandrescu, of all people, was telling.