And C++ is usually way uglier and far more complicated. I'm currently reviewing a bunch of code using bleeding edge language features and it is extremely difficult to understand what the code is supposed to do and what it might be doing.
The age of multi-vendored languages like C or C++ for systems programming might be coming to an end. I'd wager this is actually a good thing.
I have been using Rust extensively for over 2 years.
In those two years, the language complexity has increased substantially. It's already at a stage where I'm worried about losing track.
Also, there is quite a large amount of changes in the pipeline that were already accepted but are either not finished or not implemented yet. All of those can/will have a significant impact on idiomatic code and API design and will increase the complexity burden:
* specialization * generic associated types (a simple form of higher kinded types) * async/await + generators * existential types * const generics ...
So I do believe Rust needs to slow down considerably and get much stricter with accepting new features. Both to not overwhelm the existing userbase and to not make the language another C++ in terms of complexity.
And while I think Rust could do just fine without GATs, specialization is something I'd really hope for.
People will never be satisfied though and always ask for new features.
So I don't agree that the age of "The age of multi-vendored languages like C or C++ for systems programming might be coming to an end.", unless you mean we will managed to get rid of all OSes written in them, specially UNIX flavours or GPGPU libraries.
It would be nice, but it won't happen until the next big hardware revolution like Quantic computers being developed in Q# for example.
That's because you can't make money writing compilers anymore.
I thought that sqeezing MIR in the middle Rust could get both Rust-specific transformations and then benefits from the LLVM generic code optimizations...
LLVM is fantastic, but it's also a huge C++ component, and it's not necessarily optimized for speed. It would be nice to end up with something in Rust to simplify the toolchain needed, and to maybe have something that could be even faster for debug builds.
It's sort of another worse-is-better dichotomy.
As you gain experience in Go, you learn idiomatic ways to do things and memorize the core library, but the fundamental mechanics don't change.
As you gain experience in Python, you develop a preference for virtualenvwrapper and experiment with 300 different ways to build a distributable package, then start extending __getitem__ and __setitem__ and adding decorators to everything and before you realize what you've done, you've torn apart the laws of physics and your code has nightmarish side effects lurking around every corner.
Alternatively, you glimpse the chthonic horrors lurking at the end of that path, purge your code of hidden mutable state, and use Python to write straightforward procedural programs.
The move from C++ -> Java -> Go is a perfect example of this cycle.
C++ is a language designed to enable more communication between the compiler and the programmer.
Go is a language designed to enable more efficient communication between programmers.
C was hardly efficient in the 80s micro-computers, and outside Bell Labs people were doing compiler optimization research in languages like PL.8 and similar.
Had Bell Labs been allowed to sell UNIX and history would certainly looked much different.
For instance, Fortran is very performant in numerical computing, even outperforming C, but it has difficulty in accessing I/O mapped registers or implementing an interrupt handler, a jump table, or just cleaning a particular chunk of memory addresses.
"Oh, it was quite a while ago. I kind of stopped when C came out. That was a big blow. We were making so much good progress on optimizations and transformations. We were getting rid of just one nice problem after another. When C came out, at one of the SIGPLAN compiler conferences, there was a debate between Steve Johnson from Bell Labs, who was supporting C, and one of our people, Bill Harrison, who was working on a project that I had at that time supporting automatic optimization...The nubbin of the debate was Steve's defense of not having to build optimizers anymore because the programmer would take care of it. That it was really a programmer's issue.... Seibel: Do you think C is a reasonable language if they had restricted its use to operating-system kernels? Allen: Oh, yeah. That would have been fine. And, in fact, you need to have something like that, something where experts can really fine-tune without big bottlenecks because those are key problems to solve. By 1960, we had a long list of amazing languages: Lisp, APL, Fortran, COBOL, Algol 60. These are higher-level than C. We have seriously regressed, since C developed. C has destroyed our ability to advance the state of the art in automatic optimization, automatic parallelization, automatic mapping of a high-level language to the machine. This is one of the reasons compilers are ... basically not taught much anymore in the colleges and universities."
-- Fran Allen interview, Excerpted from: Peter Seibel. Coders at Work: Reflections on the Craft of Programming
The reason why older languages are complex and inelegant is because our understanding of how to create simple yet useful languages has increased, not because older languages accumulate much complexity.
One of the big differences is that Java and Go have straightforward context-free grammars, mostly, and you can whip up a working parser in no time. C++ is a bit of a beast, by comparison (hence keywords like “typename”).
C++ also has the complex overloads and template system. I think people underestimate how complex these things are when they are learning C++, and how complex their interactions are. Then there’s the preprocessor.
You can kind of argue that these are just an accumulation of changes, but other languages contemporary with C and C++ do not suffer from these complexities, so the argument falls flat. By comparison, Go and Java rely on reflection or code generation more, and these are a bit simpler.
And then there is arcane stuff like this:
* https://en.cppreference.com/w/cpp/language/eval_order
* https://en.cppreference.com/w/cpp/language/copy_elision
That does make the language feel very complex even without the whole templates depth.
https://www.youtube.com/watch?v=_ahvzDzKdB0
or as PDF:
https://www.cs.virginia.edu/~evans/cs655/readings/steele.pdf
It is like designing a workshop. You only have so much space within arms reach. Here you place your most frequently used and valuable tools. You can "grow your language" by adding tools but they can't replace what you have in this limited and privileged position - instead, new tools have to go in cupboards or on another table. The new tools will have higher friction than the first priority tools you added.
Maybe you can have a workshop where you imagine a tool and it appears in your hand thereby removing the constraint of "tools within reach" but I think this then makes it too ephemeral and abstract. It is the constraints that makes a language tactile and ergonomic - remove the constraints and you have no structure at all.
In this case, the "tools within reach" are your core keywords and syntax. Growing features via libraries or syntactic extensions generally incur more ceremony and less elegance. Having totally flexible syntax extensions/keywords doesn't solve the problem, it just moves up a level abstraction/generality and means you have given your users the "design a language" problem instead of solving it for them.
This is an interesting observation that reminds me of
> In Lisp, you don't just write your program down toward the language, you also build the language up toward your program.
from Paul Graham's "Programming Bottom-Up". Some people would consider it an advantage being allowed to fold the "design a language problem" into the overall problem.
More, I think this is just a matter of degree, not kind.
As soon as you have something as apparently simple as named procedures, you're really writing a DSL, albeit very coarsely, for your business problem. Add named record types. Add textual macros. Add operator overloading. Add Lisp-style macros. At every point where the language allows a word on the screen to stand in for something larger, you're giving the programmer the power to design a language for their problem domain.
> We need to put tools for language growth in the hands of the users.
Guy's previos work with Common Lisp is epitome of this. There are three kind of macros in the language: reader macros, macros and compiler macros. They give the tools for the user to extend the language. There is just 25 or so core primitives in the 'core' language and the runtime. The rest 900+ functions and symbols are basically the standard library (the fact that they are slapped into the same package and they extend the core in a ways that other languages can't hides the simplicity of the language somewhat).
The creator has repeated this philosophy a few times:
> There is very little reason for an Elixir v2.0 with breaking changes. The language was designed to be extensible and if we need to do major changes to the language to improve the language itself, then we failed at the foundation.
https://elixirforum.com/t/what-would-you-like-to-see-in-elix...
> A big language does not only fragment the community and makes it harder to learn but it is also harder to maintain. It is also why the language was designed to be extensible: so the community could build what is necessary without a push to make everything part of the language.
https://elixirforum.com/t/questions-about-property-testing-s...
> We also understand there is a limited amount of features we can add to the language before making the journey too long or the language too big. Adding something now means not including something else later. As an exercise, let’s see a counter example of when we didn’t add something to the language: GenStage. [...]
https://elixirforum.com/t/questions-about-property-testing-s...
C++ were very performant, and unlike Java not a memory hog, but for this sort of tasks it was deemed to complex. Besides the ammount of people that were able to program in it was limited (and you can even see this reasoning being used as a reason why they created Go).
Then Go showed up with simplicity and a good story in performance and memory, deploying simple binaries in a world of complex server infrastructure paved out by Java was a very handy and right on time approach.
The problem with Rust now, is that it is target the system programming, in a era of system programming renaissance given the whole mobile app scenery.
The problem is; there is already a lot of C++, C, Java, Objective C and Swift code there, so it will be hard to have a good reason to rewrite complex stuff with a lot of man hours in it in Rust.
Every language needs a platform. C is here because of Unix, C++ because of Unix, games and early 2000 and 90's era startups. Java and C# because of bussiness software, Go because of the cloud, Ruby and PHP; because of webservers, Rails an blogs, Python for educational purposes, Data Science, ML and now AI.
Now what about Rust? Its trying to eat some lunch from C and C++. But a lot of code and value is already there in C and C++, where rewriting it in Rust just for some ocasional added value here and there is not reasonable.
Its really tough, and if Rust dont find a platform to grow it will be very hard to advance any further. But, there's always new tech waves and tools that will marry perfectly with them.
Lets see which languages are able to be a perfect fit for them in years to come. But right now Rust will depend a lot of the community it already formed to keep sharp and maybe be lucky to surf one of those waves.
For existing software it certainly is less about rewriting it but extending it in Rust.
Thanks to not having a runtime and being quite easy to create a shared library with a C-ABI, pretty much any software written in any language could be extended with code written in Rust.
Ok, but you kind of added complexity to your codebase. Now you will need not only C++ coders, but also Rust coders. If you are Mozilla or Google you can do that, you eat complexity for breakfast.. But they are probably not the companies you need right now to keep your steady growth, unless of course you are lucky that they created some killer app in Rust because of their in-house use of it. Like Google did recently with Kubernetes, only it is in Go of course, but i guess it checks the square of a killer app which helps into the language adoption as a programming trend.
Speaking of Go, it once was suffering from the same problem Rust is going through right now. It was sucessful in a first phase because of its community, but it needed more to start having more adoption and mind share. Then, Docker happened, and Go found a sweet spot to aim for and take it to the second base.
Another platform is cryptocurrencies/blockchains- Rust lends itself uniquely as a modern C++ for writing performant blockchain protocols. This is evidenced by the Parity team using Rust exclusively for all their projects. As that world evolves Rust can find a foothold there as well - there is no real legacy code floating around, the Bitcoin protocol is only 10 years old.
The blockchain trend reminds me that Rust could also try to find some cosy home in banking software, in expert systems and in embedded software.
The key now is to be humble and not to try to pick up a fight with C and C++ just yet.
Go only now is in a position to pick a fight against Java in the server and in the cloud, and Rust will probably need to get into the same point of adoption, if it wants to be seen as a serious contender to those languages in the eyes of the people they need to convince.
Unfortunatelly Rust missed a good new trend now, with multi-platform cores for mobile phones that ended up being written mostly in C++.
Rust wasnt even invented when people were trying to solve the problem of multi-os phone apps.
And that is the current trend which is giving the system development trend a second chance. But is not that bad, because thats exactly what is making people to take a serious look at Rust.
Battery life, memory, storage. It all matters again thanks to the phone environments, and you cant afford the be wrapped in layers upon layers of software virtualization like it was common in the early PC era, where Java and C# were kings.
Why do you think that Rust will be a different story this time?
Nothing -- not even C++ -- "killed off" C.
But it's share of total usage can and will decrease. Java certainly took a massive chunk of the "programming market" that would have otherwise been C++. And then later JavaScript. Of course, software usage in general has been going up and up, so in absolute terms C++ has been increasing as well.
It certainly looks much different in other platforms and modern OSes.
C is very conservative in adding new features, which makes it easy to get an understanding of the language - nothing is hidden, when necessary you can derive everything you need to know about a piece of code from first principles.
C compared to C++ feels like high school math compared to university math with all it's scary notations.
Certainly not in the C compilers that have been ported to C++, the OSes that now push for C++ instead of C, automative standards that replaced C by C++14 as certified language, IoT platforms based on C++,...
The complexity difference between a computer 46 years ago, almost half a century, when C was first developed and now is incomprehensible. It is reflected in how these languages, fundamentally, are willing to just say "since the computer only runs in 30 cycles a second and we are just doing increment operations if you break the rules it undefined" and the idea of having behavior native to the grammar that would impose kilobytes of size complexity or megacycles of computation would have been absurd.
At the time, of course. Modern C++ doesn't hesitate to generate multiple vtables for a complex class while juggling reference counting smart pointers with streamlined initialization grammars that can take up thousands of words of memory. But because the primitives that must remain stable for backwards compatiblity were built on the most constrained architectures the whole house of cards is thus constrained.
I think Rust has already made a lot of "just get it out there, don't think about the ramifications" decisions in its language design, especially around its grammar. On one hand there is still logic to what glyphs do what and how control flow is laid out - particularly concerning minimizing the number of retraced parses the source needs to go through - but it doesn't change the syntax of lifetimes ('), the use of double colon namespacing (::), or the turbofish but being an unintitive disgusting hack.
In my experience the RFC process though has helped temper that. All those blemishes are predominantly from the origin of the language up through ~2014. Once the ecosystem developed and decisions required months or years of hanging around on the issue tracker to see stabilization Rust mostly stopped grafting on arcane behavior and started reusing its syntax in intuitive ways (like impl trait).
I don't think any of the current crop of outstanding wants really makes the language more complex. A lot of it is just increasing the compliers complexity to handle more generic code, be it in terms of kindedness or by type delegation or inference. Those kinds of complexities aren't generally something for users to explicitly learn in rote memorization to use the language - if Rust introduced overloading and default arguments all those who don't know it exists won't have the language made any harder to use, but those that want it can take advantage of it.
As long as Rust is never tempted into the "a substantial portion of this behavior is undefined if not done explicitly as intended" hole that C and C++ were predestined to fall upon I don't think features make the language harder. The core syntax is there, shouldn't change, and is being made easier and more intuitive, not harder and more complex (NLLs save the day, and the number of explicit lifetimes needed have been culled dramatically in the last two years of development). In just a few glyphs of C++: void* = 0 - there are already all manner of edge case, undefined behavior, syntactic complexity beyound comprehension.
Nobody working on Rust today wants arcane, impossible to process grammar or undefined behavior complexity. The language already mostly lacks it. Nobody in their right mind will add it. Thats never a feature, its the bug Rust fixes that is inherent to the C lineage in ways nothing but a rewrite can fix. It took a decade of pain for Python to change its string type from bytes to unicode - there is absolutely no way to salvage C or any of its descendants. By design. From its fundamentals. All the complexity on top has just been repeated attempts to temper the fundamental incongruities.
The following program might no longer compile if `f` could be overloaded, additional type annotations would be required.
fn f(n: u8) -> u8 {
n + 5
}
let o = Some(Default::default());
println!("{:?}", o.map(f));
In general I would expect overloading to make type inference less reliable (not so much with the compiler getting it wrong, but needing more annotations), which users of Rust that do not want overloading would be right to object to.I'd argue you could not implement overloading without enough support being in place to let the current single signature inference work as it does, which is also probably a good chunk of why its never been really proposed in earnest. Its a hard problem to approach.
> Those kinds of complexities aren't generally something for users to explicitly learn in rote memorization to use the language - if Rust introduced overloading and default arguments all those who don't know it exists won't have the language made any harder to use, but those that want it can take advantage of it.
This however is only true if you never have to work with someone else's codebase, When you start creating dialects your usual 'style' will feel alien in another project or you may have trouble understanding how something works due to an arcane language feature you've never had to use.
There are a lot of best practices that can be quite hard to follow without language support, and would generate too many false positives to warn about. For instance, use after free. There is no lifetime information available in the type system that the C compiler could use to tell you whether you are doing something that could result in a use-after-free. How do you propose the compiler could catch these, except in the most trivial cases?
I think that's pretty terrible advice for something that affects memory safety, and it invalidates your entire "the compiler will warn you" argument. There's a reason Rust has ownership and lifetime annotations: there are many things that a compiler simply cannot infer.
I wouldn't say it's terrible advice though. If I see that something I do is potentially dangerous and would require special care to get it right, I'd first look if there is another way to do it.
References are always guaranteed to point to something that lives longer than them. What they refer to could either be on the stack, or it can be allocated and deallocated on the heap via smart pointers. Box<T> is similar to C++'s unique_ptr; there is a single owner, and when that owner is dropped, the referenced value is dropped. Arc<T> is similar to C++'s shared_ptr; it's an atomically reference counted pointer, so the value will be deallocated when the last clone is deleted.
These combine in ways that allow you to use most of those "best practice" patterns from C and C++, but with actual language guarantees that you will follow them and the compiler will catch it if you don't. In the internals of data structure implementation, you may need to go beyond what this allows with use of unsafe, but you can limit that just to a few small critical pieces of code that can be more thoroughly audited and tested.
In a large project "try to avoid doing that" doesn't really fly; in a large codebase, with dozens of developers, developed over multiple years, people are going to make mistakes. They will need to do some kind of refactoring, and one of the guarantees will get missed.
If you've done a decent amount of work in a large C or C++ codebase, you might appreciate this description of the process of storing a reference to a slice of a Vec in Rust, without having to manually reason about all of the places in which that slice could be invalidated: https://manishearth.github.io/blog/2015/05/03/where-rust-rea....
This article, written as a way to help introduce Rust 1.0, provides a few more examples, though they are artificial rather than from a real-world project: https://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.h...
I think it's bad advice because you need to know what is potentially dangerous. That alienates newcomers to systems programming (which is why one of Rust's goals is to be able to "hack without fear"). There are also some designs that I'd never try to build without safety checks, because they're notoriously hard to get right. Even knowing the fundamentals of memory safety, without a way of enforcing lifetimes and mutability, writing zero-allocation implementations that share immutably can be daunting.
[0] Cell, RefCell, Mutex, Rc, Arc
What's got people excited is the evolution of these systems which have a rich intersection between rigor and usability (to include ecosystem, tooling, etc.). The ability to statically eliminate classes of error with good (or maybe good enough) ergonomics means these questions of "how frequently does this occur" are not relevant - you can drive such occurrences to zero and doing so should become table stakes. This is a proper engineering mindset.
None of this is to dismiss craftsmanship, which definitely has its place. And before the ability to practically apply rigor, that's all there was. After all, people have been building complex systems for centuries without the benefit of modern mathematical foundations. But none of those are good reasons to eschew rigor when it is within reach and when the systems you develop are big/complex/widely-used enough.
- the number of users
- the amount of mission-critical legacy code that has to compile
- the number of compiler vendors
- age
... comparable to C++’s.
Did you hear they’re finally making it object oriented? The name will be ADD ONE TO COBOL
I think that was supposed to be a joke, mocking Cobol's verboseness (compare "C++", which adds 1 to C and which extends C with OOP).
It's a new language targeted toward people who want stability and performance, what do you expect to see by now?
When? 2018? 1990?
So far that is only true of Redox.
The change isn't as marked as say perl5 and perl6 but it is still huge.
I've no dog in either fight though, I moved to managed languages a long time ago (Object Pascal was the last compiled language I used in anger).
Not sure which managed languages you mean, those that I use also provide the option to compile to native code just like Object Pascal, if I wish to do so.
(And I also elaborate more about specifics in my other sibling comment)
To what extent is Rust being adopted by the Fuchsia team?
For example, {} style initalisers were added to simplify and "unify" things. Except to make a vector of length 3 you still need to use the old style (3) notation. So now there is just one more thing to learn.
It is much easier to add "one more thing" to a language than it is to later take it out.