> ready to use tooling support
This really depends on the use case. If the tooling you're looking for is "Unreal Engine", then Rust is definitely not there yet. But if you want to write network services, databases, CLI tools, etc., I think Rust has been fully viable with good tooling and library support for years now.
> easy to hire if project expands
No doubt hiring experienced Rust developers is hard, because there aren't that many yet. But anecdotally, training Rust developers is much easier than training C++ developers. Rust makes it easier for one or a few experienced folks to have confidence that a larger number of less experienced contributors aren't introducing stability or security issues.
Picking Java over kotlin for android, or lumping C# into "just for windows" bucket is exactly the ignorance that leads you to "we don't have choices" conclusion. If you always default to literally top1 language for given domain, then how can you expect to have multiple choices?
I'm not talking about using some brand new gimmick that came out yesterday and will die tomorrow, but if you aren't able to leave your comfort zone even just-so slightly to update your stereotypes about given given domain and its trends, then by definition you won't see the alternatives.
Python is the traditional scripting language for high-performance computing (HPC) going back a very long time, as a wrapper for highly optimized and scalable C libraries. Python on supercomputers wasn't originally used for machine learning but it naturally included support for excellent linear algebra libraries, etc for various types of large-scale modeling. When machine learning first became trendy, data scientists that wanted access to mature library implementations of critical algorithms that worked very well at large scales were mostly limited to C/C++ or Python wrappers of that C/C++. Naturally, they chose Python for the same reason Python was already being used in HPC -- ease of use.
By virtue of its use in supercomputing, Python had uniquely strong support for many types of machine learning before there was a demand for machine learning. If HPC had used some other popular scripting language instead of Python, we'd probably be using that for machine learning right now.
We are doing L4, so if something goes wrong, the manufacturer is responsible. Once we were in a test car. It was behaving great. At one point, the car "makes the decision" to accelerate and pass. The manager to which we were showing asks "we did the car not waited a little bit? can we change that behavior?". A very long discussion ensued, about data sets, labeling, training... long story short: we have to be able to change behavior (even for perception) in a deterministic way. So the amount of NNs went down dramatically after that.
Even if the given reason is simple, like "I decided to slow down because the car in front of me is red", explicit override of the learned rule ("don't slow down when you see red cars") might potentially increase the chance of the accident by a lot more than 0.0002% because we are messing with the model decision making process.
What's the alternative for quick ML prototyping?
Java/Scala I've also seen, specifically things around Spark ML, and Spark compute.
That said, Python is definitely the most common nowadays I'd say.
What made it hard? For example, Rust seem to fit your bullet list.
You could just use C.
Any big-enough system will naturally have some OO-ish parts, just because it has some of almost everything. Most small programs will have none.
They're techniques after all, models, tools.
I have worked on template-heavy codebases that would really benefit from concepts, though.
That plus structured bindings return types. :)
The new features are added specifically because they enable writing better programs. Avoiding them means you are choosing not to write better programs. It is allowed, but not a thing to brag about.
That does not mean every program has to use every feature. But when there is a choice between the new way and the old way to do something, the new way is very probably better.
Passing a reference, where you can, is better than passing a pointer. Passing a lambda is better than passing a function pointer. New for-loops are better than old for-loops. A unique_ptr is a better member than a naked pointer. A compiler-generated destructor is better than a hand-coded one.
And so on.
C is the language that invites in mistakes. The less C we code, the fewer mistakes we ship.
A large amount of C++ development is still focused on enterprise and UI apps, even though one could argue that it is not the best language for that purpose.
Someone needs to write a c++ 101 book, followed by 201 and 301 for beginner, intermediate, and advanced users.
But then, it's hard to find good programmers for any language, so C++ isn't really any worse off here, and better off than some in that at least there's a bunch of candidates to pick from.
I like to live in the embedded world and there I think Rust will shortly start to push out C and C++ at the wide-spread, commercial level. This is where my head is at, maybe it helps you formulate your opinion:
First, embedded C++ is not the same a full C++ (with features or library parts missing to accommodate the constrained environment). So I might never see some of the C++2x or even C++1x features. I really don't care about the C++2x standards since I might not see them in the next 10 years.
Second, memory and concurrency issues are real, even when you don't allow dynamic memory allocation after the system is initialized. We test, then test, and then run more tests, and turn on a lot of linters and analyzers but at the end of the day, they're bandaids. You can write terrible code in Rust, but you have to work harder to do it.
Third, if you really need an environment where memory is as fungible as play dough but want something portable (like inside a kernel), there will always be C. Setting up a C callable environment from assembly is well understood. In theory, calling into Rust shouldn't be any harder. But reading disassembled object files from C is generally reasonable assembler for the C I wrote. I haven't really tried doing that with Rust.
Fourth, it will take 5 years or so, but eventually we'll see commercially supported RTOS implementations for Rust that help companies to shield themselves from liability. For example, something like Threadx (AzureRTOS now). This will help push money in a virtuous cycle of better tooling -> more projects -> more money for tool venders -> better tooling.
Fifth, there will be Rust bindings to cover just about everything you do with C++ now (like game development) with high quality, idiot-proof, and portable semantics. You will find that junior developers or fresh-outs will have their skin crawl when asked to write "unsafe" code.
Sixth, there's counter evidence to support a lack of adoption of new languages. Ada provides many of the same concurrency and memory safety guarantees as Rust, and has been available since 1983, but it is not widely adopted. It is harder to use and requires significant developer training. If Rust is significantly more challenging for developers, it may go the way of Ada.
But that's in the future. If I had to start a new project today (for a braking system on a train, for example), I would start with C, a validated RTOS, and commercial toolchain. If I had to light up my kid's halloween costume? Embedded Rust as a learning exercise. The rendering engine for a VR headset (with limited batter and compute), probably C++.
Rust is the only real competitor, but given the sheer amount of existing C and C++ code, the idea that they will become legacy languages any time soon is pretty out to lunch.
A lot of command line tooling is being written in Go along with systems software (like basically all of container land). Maybe some of these would have otherwise been written in C or C++. For example, if you use the Azure storage GUI to manage storage the underlying "thing" that copies data from your filesystem to azure storage is written in Go and that's why it works on Windows, Linux, MacOS. C++ is somewhat more portable than C so maybe something like that would have been written in C++? Who knows? But Go is definitely showing up in all sorts of native, command-line stuff. And Rob Pike - one of the people who developed Go - intended it to be a new home for C++ developers as well.
Rust is definitely difficult to learn, but I'll anecdotally say that Rust helped get me into the embedded world as someone with very little C or C++ experience. Unlike Ada, Rust has a modern toolchain and type system that feels very comfortable to developers coming from higher level languages, even if many of the concepts related to manual memory management remain quite difficult.
We have a modern toolchain too :)
# install compiler and build tools
alr toolchain --select
# make, build and run a project
alr init --bin my_project
alr build
alr runThey might both have a rule like: AV Rule 1 Any one function (or method) will contain no more than 200 logical source lines of code (L- SLOCs).
But Ada would probably not have something like: AV Rule 59 (MISRA Rule 59, Revised) The statements forming the body of an if, else if, else, while, do...while or for statement shall always be enclosed in braces, even if the braces form an empty block.
Or: AV Rule 193 (MISRA Rule 61) Every non-empty case clause in a switch statement shall be terminated with a break statement.
Those two arise from deficiencies in the basic C/C++ syntax. They are probably not fixable in a future revision of the spec because they would break too much code. So we create a coding standard, configure a linter to check for it, wire up our source repository to scan for it on checkin, and make all the leads code review for it.
Ada might have different language specific standards. For example, around not allowing breaks out of infinite loops. Requiring, instead, a loop construct with a testable condition. But even then, you can pragma things out of the compiler to help you enforce behavior. That's "built in."
My comment with Rust is not that Ada is bad or too hard, but rather even a small amount of additional work might be enough for developers and organizations to avoid Rust. There's a lot of Rust advocacy, which is a good thing. However, there was also a lot of activity on comp.lang.ada.advocacy (if I remember the newsgroup correctly). One good thing is that Ada felt "imposed" by a number of people and that automatically stimulates a kind of rejection response. Rust's introduction is definitely more "bottom up."
It isn't that hard to define something like MyType_t which is a pointer. Then some C code to allocate to wrap the MyType so the type is usable from any language.
While some applications written in C++ are not performance sensitive, performance tends to be a major objective when choosing C++ for many applications.
I think this might have been fixed in latest versions of Java, though, not sure if value types are already in the language or just coming soon.
Aside from that, it's my understanding that GC can be both a blessing and a curse for performance (throughput), that is, an advanced-enough GC implementation should (theoretically?) be faster than manual memory management.
There are a few different ways a GC impacts code performance. First, even low-latency GCs have a latency similar to a blocking disk op or worse on modern hardware. In high-performance systems we avoid blocking disk ops entirely specifically because it causes a significant loss in throughput, instead using io_submit/io_uring. Worse, we have limited control over when a GC occurs; at least with blocking disk ops we can often defer them until a convenient time. To fit within these processing models, worst case GC latency would need to be much closer to microseconds.
Second, a GC operation tends to thrash the CPU cache, the contents of which were carefully orchestrated by the process to maximize throughput before being interrupted. This is part of the reason high-performance software avoids context-switching at all costs (see also: thread-per-core software architecture). It is also an important and under-appreciated aspect of disk cache replacement algorithms, for example; an algorithm that avoids thrashing the CPU cache can have a higher overall performance than an algorithm that has a higher cache hit rate.
Lastly, when there is a large stall (e.g. a millisecond) in the processing pipeline outside the control of the process, the effects of that propagate through the rest of the system. It become very difficult to guarantee robust behaviors, safety, or resource bounds when code can stop running at arbitrary points in time. While the GC is happening, finite queues are filling up. Protecting against this requires conservative architectures that leave a lot of performance on the table. If all non-deterministic behavior is asynchronous, we can optimize away many things that can never happen.
A lot of modern performance comes down to exquisite orchestration, scheduling, and timing in complex processes. A GC is like a giant, slow chaos monkey that randomly destroys the choreography that was so carefully created to produce that high-performance.
My comment is about the thinking behind making this decision, C++ or not. It wasn't "is it speculative that GC will add a cost?" or something like that.
I wonder how much of the thinking that leads one to conclude "I need so much performance here that I can't afford a managed language", for example, is real carefully thought vs. speculative.
GC overhead is always hard to measure except end-to-end, because it is distributed over everything else that happens. Cache misses, TLB shoot-downs. Mitigations are very difficult to place.
Practically, you usually just have to settle for lower performance, and most people do. Double or triple your core count and memory allocation, and bull ahead.
I'm with you. One should look at latency requirements and the ratio of profit vs server costs when making a decision. AKA when your product generates $250k/month, you're paying three programmers $40k/month, and your AWS bill is $500/month isn't the time to try and shave pennies.
Go is a good replacement for C and C++ for almost all purposes.
Most purposes where Go is inapplicable should be using explicit SIMD (GCC intrinsics) or CUDA C anyway.
The others purposes where Go is inapplicable are low-level real-time stuff that should be written in C for a specialized software stack (e.g., software in a car).
I like go, I use it on occasion. If I stick to the simple stuff it is really easy to think about and if it is just a back end on a big server, why not. But that is a niche case for me. I usually prototype in matlab, then implement in c++, and yes, often cuda too, but I think saying that go is almost always a drop in for c++ is missing the vast majority of what c++ is used for. Go is not a systems language, it benchmarks slower than many vm lamguages like java and c#. It's gimmick is that it compiles really fast and is easy to reason about, so it is good for velocity. But it sacrifices a lot for that. FFI compatibility, and runtime speed foremost. Sometimes those things don't matter. But for systems programming, dsp, embedded, or AAA games they are deal breakers.
For example, the C/C++ implementations use arena allocators, and they could do the same for the Go implementations, but they don't.
The Benchmark Game is a joke. Here it is, for anyone who wants a good laugh: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Using c++17 (don't know 20 yet) for very small embedded targets works fine, easy FFI into the BSP, small to no runtime depending on usage.
Trying to target that with go would add a lot of extra challenge.
I think something like rust or preferably a subset of it for c++ ise cases makes sense. My next project without a GPU I have penciled in rust on the plan. The other developers are excited. Still too hard to use gpu with it. Or rather too immature.
The other developers like go too, but throughput will be critical without the gpu, and every little bit will count. And for numeric code it wouldn't just be a little bit of a hit. I wouldn't even suggest julia, and that is as close to a do-it-all language as I have seen.
You can use mark and sweep, you can us reference counting, you can use arenas, you can use local stack, you can use shared memory, you can have custom allocators for any object, you can avoid memory allocations altogether, and any combination of these.
C is the same. Java can do some of this, if you twist it hard enough. Go cannot. That's ok -- often, even most of the time, you don't care, and then Go is a fine language.
But C++ is useful in a much wider set of domains than many people think, because it offers a level of control that many other languages don't.
My favourite analogy? Go (etc) is like an Apple product: smooth, and shiny, and tries to be idiot-proof, but you can only do it the Go way. C++ is more Linux: a mess of incompatible, incomplete, infuriating things that let you do absolutely anything you want, if you invest the effort.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Go also has the stop-the-world GC problem, GC is great but it does have a price tag.
I like Go a _lot_ and use it in some projects, but I certainly will not claim it can replace C++ 'generally', not at all.
But for pretty much everything else (see the caveats in my comment above) you are better off, a lot better off, using Go.
Go is very good for many things but it does not offer competitive performance for these types of applications because design elements that have a large impact on throughput are poorly supported.
Are you saying Go has portability problems?
Can you elaborate on that very surprising claim?
The compiler is a huge assemblage of C++ templates, stitched together with a little Python.
The generated code is in an assembly language for a highly parallel machine, and needs to be heavily optimized down to the cycle.
But the compiler itself does NOT need to be so minutely tuned. It needs to be maintainable and it needs to allow easy development of complex code generators.
Go would be a great choice for the compiler.
Instead, we struggle with C++ templates, CMake'ing our code slowly and with absurd complexity, modifying the compiler slowly and with great difficulty...
Also, you should not discount compiler speed completely. You, yourself, were upset at the speed of a compile process. All of your users are going to be waiting to get things done while the compiler is running, so you might want to do some math as to how much a 10% slowdown would cost in developer salaries. The makers of gcc, LLVM, and clang spend a lot of effort on improving the speed of their compiler to make sure that you have minimal time waste.
Get a handle on your code quality, and put the language features to work to solve problems for you. Then Go will increasingly look pitifully inadequate.
The issue is that, for example, you might be getting people with poor software engineering training per se. You risk hiring some very smart phd that writes code that works and runs really fast, but isn't that readable, extendable, maintainable, testable, etc.
Javascript for the web, Python for machine learning, C for low level and system coding, Go for some native cloud and DevOps, Java for enterprise or Android, C# if you're a windows developer, Swift if you are doing Apple, we actually don't have a lot of choices when you need deliver things faster.
I played with nim, ziglang and Rust, but it's hard to use them for real product developments at this point for me.