Back in the 90s, I implemented precompiled headers for my C++ compiler (Symantec C++). They were very much like modules. There were two modes of operation:
1. all the .h files were compiled, and emitted as a binary that could be rolled in all at once
2. each .h file created its own precompiled header. Sounds like modules, right?
Anyhow, I learned a lot, mostly that without semantic improvements to C++, while it made compilation much faster, it was too sensitive to breakage.
This experience was rolled into the design of D modules, which work like a champ. They were everything I wanted modules to be. In particular,
The semantic meaning of the module is completely independent of wherever it is imported from.
Anyhow, C++ is welcome to adopt the D design of modules. C++ would get modules that have 25 years of use, and are very satisfying.
Yes, I do understand that the C preprocessor macros are a problem. My recommendation is, find language solutions to replace the preprocessor. C++ is most of the way there, just finish the job and relegate the preprocessor to the dustbin.
cogman10 · 1d ago
> just finish the job and relegate the preprocessor to the dustbin.
Yup, I think this is the core of the problem with C++. The standards committee has drawn a bad line that makes encoding the modules basically impossible. Other languages with good module systems and fast incremental builds don't allow for preprocessor style craziness without some pretty strict boundaries. Even languages that have gotten it somewhat wrong (such as rust with it's proc macros) have bound where and how that sort of metaprogramming can take place.
Even if the preprocessor isn't dustbined, it should be excluded from the module system. Metaprogramming should be a feature of the language with clear interfaces and interactions. For example, in Java the annotation processor is ultimately what triggers code generation capabilities. No annotation, no metaprogramming. It's not perfect, but it's a lot better than the C/C++'s free for all macro system.
Or the other option is the go route. Don't make the compiler generate code, instead have the build system be responsible for code generation (calling code generators). That would be miles better as it'd allow devs to opt in to that slowdown when they need it.
gary_0 · 1d ago
I can't think of a C++ project I've worked on that didn't rely on being able to include C headers and have things usually just work. Are there ways of banning C macros from "modular" C++ without breaking that? (Many would find it unacceptable if you had to go through every C dependency and write/generate some sort of wrapper.)
WalterBright · 1d ago
D resolved this problem by creating D versions of the C system headers.
Yes, this was tedious, but we do it for each of our supported platforms.
But we can't do it for various C libraries. This created a problem for us, as it is indeed tedious for users. We created a repository where people shared their conversions, but it was still inadequate.
The solution was to build a C compiler into the D compiler. Now, you can simply "import" a C .h file. It works surprisingly well. Sure, some things don't work, as C programmers cannot resist put some really crazy stuff in the .h files. The solution to that problem turned out be we discovered that the D compiler was able to create D modules from C code. Then, the user could tweak by hand the nutburger bits.
fooker · 5h ago
> The solution was to build a C compiler into the D compiler.
This is the same solution that Apple chose for Swift <-> Objective C interop. I wonder if someone at Apple was inspired by this decision in D!
Calavar · 1d ago
Did anyone reach out to you for input during the modules standardization process? D seems like the most obvious prior art, but the modules standardization process seems like it was especially cursed
WalterBright · 1d ago
Nobody from C++ reached out to me for the modules.
Herb Sutter, Andrei Alexandrescu and myself once submitted an official proposal for "static if" for C++, based on the huge success it has had in D. We received a vehement rejection. It demotivated me from submitting further proposals. ("static if" replaces the C preprocessor #if/#ifdef/#ifndef constructions.)
C++ has gone on to adopt many features of D, but usually with modifications that make them less useful.
jjmarr · 1d ago
static if was more or less added in C++17 under the name `if constexpr`. It's not exactly the same since the discarded statement is still checked if not dependent on a template, but like most things in C++, it's similar enough to footgun yourself.
sfpotter · 1d ago
"if constexpr" introduces a new scope, while "static if" does not. A major divergence, enough to make the features very, very different in terms of what they can actually be used for.
WalterBright · 1d ago
That was a giant mistake on C++'s part, as you cannot do this:
static if (feature) { int bar() { betty(); }}
... lots of code ...
static if (feature)
bar();
Forcing a new scope cuts the utility of it about in half, and there's no way around it. But if you need a scope with D's static if:
static if (expression) {{ int x; foo(x); }}
the extra { } will do it. But, as it turns out, this is rarely desirable.
jjmarr · 1d ago
I would use std::enable_if or C++20 concepts. Both work fine for selectively enabling functions.
sfpotter · 22h ago
C++20 concepts are a step in the right direction, but:
- D had this feature long before C++ did.
- It isn't the same thing as "static if". Without "static if", conditionally compiling variables into classes is much more elaborate, basically requiring subclassing to do, which is not really semantically how subclassing should be used (the result is also way more confusing and oblique than the equivalent directly expressed construct you'd have using "static if").
quotemstr · 1d ago
Yes, but not for accommodating missing definitions or platform specific syntax. A true static if MUST allow invalid syntax in branches not taken if it's to fully replace the preprocessor.
repstosb · 5h ago
I've often wondered how the evolution of C and C++ might have been different if a more capable preprocessor (in particular, with more flexible recursive expansion and a better grammar for pattern matching) had caught on. The C++ template engine can be used to work around some of those limits, but always awkwardly, not least due to the way you need Knuth's arrow notation to express the growth in compiler error message volume with template complexity. By the time C++ came out we already had tools like m4 and awk with far more capability than cpp. It's pretty ridiculous that everything else about computing has radically changed since 1970 except the preprocessor and its memory-driven constraints.
agent327 · 1d ago
C++ modules aren't influenced by macros on import, nor do they export any macros, so I'm curious what the problem is?
fooker · 1d ago
What has to change in C++ templates for this to work?
It seems particularly tricky to define a template in a module and then instantiate it or specialize it somewhere else.
WalterBright · 1d ago
In order to make things work smoothly, the module has to have its own namespace, and a namespace that is closed.
D also has an `alias` feature, where you can do things like:
alias Q = abc.T;
where from then on, `abc.T` can be referred to simply as `Q`. This also eliminates a large chunk of purpose behind the preprocessor.
fooker · 1d ago
Neat, closed namespaces sound great!
C++ has adapted the ‘using’ keyword now to seem fairly similar to alias, but can’t completely subsume macros unfortunately.
WalterBright · 1d ago
Closed namespaces means the module can be reliably imported anywhere without changing its semantic meaning.
Gibbon1 · 1d ago
I've really wanted that in C for a long time. It seems like a very trivial thing as well.
WalterBright · 1d ago
Yeah, it's actually easy to implement, too.
It replaces the preprocesser:
#define Q abc.T
with hygiene. Once you get used to it, it has all kinds of nice uses.
StilesCrisis · 1d ago
C++ code generally uses `using` for typedefs. Macro-based typedefs are exceptionally rare and frowned upon.
jjmarr · 1d ago
Including or importing a templated class/function should not require bringing in the definition. That's why #includes and imports are so expensive, as we have to parse the entire definition to determine if template instantiations will work.
For normal functions or classes, we have forward declarations. Something similar needs to exist for templates.
WalterBright · 1d ago
And it doesn't in D. We call such modules ".di files", which consist only of declarations.
D does not require names in global scope to be declared lexically before they are used. C++ only does this for class/struct scopes. For example:
int bar() { foo(); }
int foo() { bar(); }
compiles and runs (and runs, and runs, and runs!!!).
jjmarr · 1d ago
> D does not require names in global scope to be declared lexically before they are used. C++ only does this for class/struct scopes.
But how do you handle a template substitution failure? In C++:
template<typename T>
auto bar(T x, T y)
{ return x + y;}
The compiler has no idea whether bar(1, 2); will compile unless it parses the full definition. I don't understand how the compiler can avoid parsing the full definition.
The expensive bit in my experience isn't parsing the declaration, it's parsing the definition. Typically redundantly over thousands of source files for identical types.
WanderPanda · 1d ago
I think modularization of templates is really hard. Best thing I can think of is a cache e.g. for signatures. But then again this is basically what the mangling already does anyways in my understanding.
fooker · 1d ago
Not just signatures, you need the whole AST more or less.
This seems incredibly wasteful, but of course still marginal better than just #including code which is the alternative.
modeless · 1d ago
The sensible way to speed up compilation 5x was implemented almost 10 years ago, worked amazingly well, and was completely ignored. I don't expect progress from the standards committees. Here it is if you're interested: https://github.com/yrnkrn/zapcc
The next major advance to be completely ignored by standards committees will be the 100% memory safe C/C++ compiler, which is also implemented and works amazingly well: https://github.com/pizlonator/fil-c
motorest · 1d ago
> The sensible way to speed up compilation 5x was implemented almost 10 years ago, worked amazingly well, and was completely ignored. I don't expect progress from the standards committees. Here it is if you're interested: https://github.com/yrnkrn/zapcc
Tools like ccache have been around for over two decades, and all you need to do to onboard them is to install the executable and set an environment flag.
What value do you think something like zapcc brings that tools like ccache haven't been providing already?
> What value do you think something like zapcc brings that tools like ccache haven't been providing already?
It avoid instantiating the same templates over and over in every translation unit, instead caching the first instantiation of each. ccache doesn't do this: it only caches complete object files, but does not avoid repeated instantiation costs in each object file.
motorest · 1d ago
> It avoid instantiating the same templates over and over in every translation unit, instead caching the first instantiation of each. ccache doesn't do this: it only caches complete object files, but does not avoid repeated instantiation costs in each object file.
I'm afraid this feature is at best a very minor improvement that hardly justifies migrating a whole compiler. To be blunt, it's not even addressing a problem that exists or makes sense to even think about. I will explain to you why.
I've been using ccache for years and I never had any problem getting ccache to support template code. Why? Because the concept of templates is ortogonal to compiler caches. It matters nothing, if you understand how compiler caches work. Think about it. You have the source file you are compiling, you have the set of build flags passed to the compiler, and you have the resulting binary.
That's the whole input, and output.
It's irrelevant if the code features templates or not.
Have you checked if the likes of zapcc is fixing a problem that actually doesn't exist?
agent327 · 1d ago
> I'm afraid this feature is at best a very minor improvement that hardly justifies migrating a whole compiler.
> To be blunt, it's not even addressing a problem that exists or makes sense to even think about. I will explain to you why.
Do you talk down to people like this IRL as well?
> I've been using ccache for years and I never had any problem getting ccache to support template code.
What I said is that zapcc has a different approach that offers even more performance benefits, answering the question of what zapcc offers that ccache doesn't offer.
> if you understand how compiler caches work. Think about it.
There's no need to use "condescending asshole" as your primary mode of communication, especially when you are wrong, such as in this case.
If you look at the benchmarks you just quoted, you see cache-based compilations outperforming zapcc in quite a few tests. I wonder why you missed that.
The ones that ccache fares as well as builds that don't employ caching at all are telling. Either ccache was somehow not used, or there was a critical configuration issue that prevented ccache from caching anything. This typically happens when projects employ other optimization strategies that mess with ccache, such as pipelining builds being enabled or extensive use of precompiled headers.
The good news is that in both cases these issues are fixed by either by actually configuring ccache or disabling these other conflicting optimization strategies. To be able to tell, it would be necessary to troubleshooting the build and take a look at ccache logs.
> Do you talk down to people like this IRL as well?
Your need to resort to personal attacks is not cool. What do you hope to achieve, other than not sounding like an adult?
And do you believe that pointing out critical design flaws is "talking down to people"?
My point is very clear: your baseline compiler cache system, something that exists for two decades, already supports caching template code. How? Because it was never an issue to begin with. I explained why: because a compiler cache fundamentally caches the resulting binary given a cache key, which is comprised of data such as the source file provided as input (basically the state of the translation unit) and the set of compiler flags used to compile it. What features in the translation unit is immaterial. It doesn't matter.
Do you understand why caching template code is a problem that effectively never existed?
> What I said is that zapcc has a different approach that offers even more performance benefits, answering the question of what zapcc offers that ccache doesn't offer.
It's perfectly fine if you personally have a desire to explore whatever idea springs to mind. There is no harm in that.
If you are presenting said pet project as any kind of solution, the very least that is required of you is to review the problem space, and also perform a honest review of the solution space. You might very well discover that your problem effectively does not exist, because some of your key assumptions do not hold.
I repeat: with pretty basic compiler caches, such as ccache which exists for over two decades, the only thing you need to do to be able to cache template code is to install ccache and set a flag in your build system. Tools such as cmake already support it out of the box, so onboarding work is negligible. Benchmarks already show builds with ccache outperforming builds with the likes of zapcc. What does this tell you?
jhasse · 1d ago
It isn't only about template code. zapcc speeds up compilation when there's no cache. I tried it 10 years ago and it really reduced build times from minutes to seconds. For full builds.
motorest · 13h ago
> It isn't only about template code. zapcc speeds up compilation when there's no cache.
Someone else in this thread already pasted benchmarks. The observation was, and I quote:
> Zapcc focuses on super fast compile times albeit the speed of the generated code tends to be comparable with Clang itself, at least based upon last figures.
account42 · 1d ago
ccache works at a translation unit level which means it isn't any better than just make-style incremental rebuilds when you aren't throwing away the build directory - it still needs to rebuild the whole translation unit from scratch if a single line in some header changes.
motorest · 1d ago
> ccache works at a translation unit level which means it isn't any better than just make-style incremental rebuilds when you aren't throwing away the build directory
You used many words just so say "ccache is a build cache".
> it still needs to rebuild the whole translation unit from scratch if a single line in some header changes.
You are using many words to say "ccache rebuilds a translation unit when it changes".
What point were you trying to make?
modeless · 1d ago
Zapcc speeds up incremental builds of a single translation unit. And it speeds up clean-cache builds that include the same headers in multiple translation units. Ccache does not. Yes, this is a huge advantage in real situations.
Frankly your attitude in this whole thread has been very condescending. Being condescending and also not understanding what you're talking about is a really bad combination. Reconsider whether your commenting style is consistent with the HN guidelines, please.
imtringued · 1d ago
From my perspective there is a pretty big difference between a persistent compiler daemon and a simple cache that constantly restarts the compiler over and over again.
account42 · 1d ago
> The next major advance to be completely ignored by standards committees will be the 100% memory safe C/C++ compiler, which is also implemented and works amazingly well: https://github.com/pizlonator/fil-c
> It's not even possible to link to unsafe code.
This makes it rather theoretical.
StilesCrisis · 1d ago
You’d be surprised! “Lots of software packages work in Fil-C with zero or minimal changes, including big ones like openssl, CPython, SQLite, and many others.”
It wraps all of the typical API surface used by Linux code.
I’m told it has found real bugs in well-known packages as well, as it will trap on unsafe but otherwise benign accesses (like reading past one the end of a stack buffer).
dgan · 1d ago
what is this sorcery. I was reading HN for years, this is the first time I see someone brings up a memory safe C++. how is that not even on the headlines ? what's the catch, build times ? do I have to sell my house to get it?
EDIT: Oh, found the tradeoff:
hollerith on Feb 21, 2024 | prev | next [–]
>Fil-C is currently about 200x slower than legacy C according to my tests
modeless · 1d ago
The catch is performance. It's not 200x slower though! 2x-4x is the actual range you can expect. There are many applications where that could be an acceptable tradeoff for achieving absolute memory safety of unmodified C/C++ code.
But also consider that it's one guy's side project! If it was standardized and widely adopted I'm certain the performance penalty could be reduced with more effort on the implementation. And I'm also sure that for new C/C++ code that's aware of the performance characteristics of Fil-C that we could come up with ways to mitigate performance issues.
jandrewrogers · 1d ago
The design choices that make it slower can’t be mitigated either in theory or practice. C++ competes against other languages that make virtually identical tradeoffs that are far more mature and which have never closed that performance gap in a meaningful way.
For the high-end performance-engineered cases that C++ is famously used for, the performance loss may be understated since it actively interferes with standard performance-engineer techniques.
It may have a role in boring utilities and such but those are legacy roles for C++. That might be a good use case! Most new C++ code is used in applications where something like Fil-C would be an unacceptable tradeoff.
modeless · 1d ago
It won't ever be 1x the runtime, but if you think better than 2x is an insurmountable challenge then we will have to agree to disagree on that one. And for high-end performance engineering, I expect code that is aware of Fil-C could do better than naive code simply compiled with Fil-C.
munificent · 1d ago
I mean, if I could accept a 2x-4x performance hit, then I wouldn't be using C++ in the first place. At that point, there are any of a number of other languages that are miles more pleasant to program in.
modeless · 1d ago
There are a whole lot of C/C++ libraries and utilities that would be great to use in a memory safe context without rewriting them. And it's not exactly easy to reach 2x C's runtime in most other languages. But again, I think that performance penalty could be significantly reduced with more effort on the implementation.
wrs · 1d ago
For new code, sure. But there is plenty of existing C code that isn’t going to be rewritten and isn’t that performance sensitive.
fuhsnn · 1d ago
Latest version of Fil-C with -O1 is just around 50-100% slower than ASAN, very acceptable in my book. I'm actually more "bothered" by its compilation time (took roughly 8x time of clang with ASAN).
saagarjha · 1d ago
For debugging, sure. Address sanitizer is itself pretty slow.
foldr · 1d ago
I’ve found Fil-C 0.670 to be around 30x slower than a regular build, with -O1 vs. -O2 not making much difference. Perhaps it is very dependent on the kind of code. IIRC the author of Fil-C (which I think is an incredible project, to be clear) wants it to be possible to run Fil-C builds in production, so I think the comparison to a regular non-ASAN build is relevant.
throwawaty76543 · 1d ago
> The sensible way to speed up compilation 5x was implemented almost 10 years ago, worked amazingly well, and was completely ignored. I don't expect progress from the standards committees. Here it is if you're interested: https://github.com/yrnkrn/zapcc
Of course it was completely ignored. Did you expect the standards committee to enforce caching in compilers? That's just not its job.
> The next major advance to be completely ignored by standards committees will be the 100% memory safe C/C++ compiler, which is also implemented and works amazingly well: https://github.com/pizlonator/fil-c
Again—do you expect the standards committee to enforce usage of this compiler or what? The standards commitee doesn't "standardize" compilers...
modeless · 1d ago
Both zapcc and Fil-C could benefit from the involvement of the standards committee. While both are very compatible, there are certain things that they can't fully support and it would be useful to standardize small language changes for their benefit (and for the benefit of other implementations of the same ideas). Certainly more useful than anything else the standards committees have done in the past 10 years. They would also benefit from the increased exposure that standardization would bring, and the languages would benefit from actual solutions to the problems of security and compile time that C/C++ developers face every day.
motorest · 1d ago
> Both zapcc and Fil-C could benefit from the involvement of the standards committee.
I think there is a hefty deal of ignorance in your comment. A standardization process is not pull-based, it's push-based.
If you feel you have a nice idea that has technical legs to stand, you write your idea down and put together a proposal and then get in touch with committee members to present it.
The process is pretty open.
> Certainly more useful than anything else the standards committees have done in the past 10 years.
Do you understand the "standards committee" is comprised of people like you and me, except they got off their rear-end and actually contribute to it? You make it sound like they are a robe-wearing secret society that is secluded from the world.
Seriously, spend a few minutes getting acquainted with the process, what it takes to become a member, and what you need to do to propose something.
wakawaka28 · 1d ago
The standards committee's job is not to develop products like software packages for free. It is to ensure that the language meets the requirements of stakeholders, many of whom develop competing commercial products.
Of course, these tools are of interest to the broader C++ community. Thanks for sharing.
modeless · 1d ago
I'm not asking the standards committees to develop them for free. They were already developed! I'm saying that the committees should acknowledge their existence, and the fact that they solve some of C/C++'s biggest problems, in some ways better than the committee-blessed solutions. They deserve attention from the committees to direct language evolution in ways that support them better and encourage alternative implementations.
wakawaka28 · 1d ago
I was referring to this:
>Both zapcc and Fil-C could benefit from the involvement of the standards committee.
What exactly does the standards committee do for these software projects without being involved in their development? I think there is nothing to do here that is within the scope of the language itself. Of course, if the creators of those projects come up with a cool new idea, they can submit to the standards committee for comment. They can also comment on new standards that make the tools not work anymore. But that is help going from the project to the committee, not the other way around.
motorest · 1d ago
> 'm not asking the standards committees to develop them for free. They were already developed!
That's great to hear. It sounds like you have everything set to put together a proposal. Do you have any timeline in mind to present something?
> I'm saying that the committees should acknowledge their existence, (...)
Oh does this mean any of the tools you're praising was already proposed to be included in the standard? Do you mind linking to the draft proposal? It's a mailing list, and all it takes is a single email, so it should be easy to link.
FWIW tooling being as important as it is, it always seems like a mistake to me that the standard committee doesn't standardize compilers.
motorest · 1d ago
> Of course it was completely ignored. Did you expect the standards committee to enforce caching in compilers? That's just not its job.
There are also quite a few compiler cache systems around.
For example, anyone can onboard tools like ccache by installing it and setting an environment variable.
wakawaka28 · 1d ago
Why should anyone use zapcc instead of ccache? It certainly sounds expensive to save all compiler's internal data, if that is what it does.
I'm sure you must be aware, these compiler tools do not constitute a language innovation. I'd also imagine that both are not productions ready in any sense, and would be very difficult to debug if they were not working correctly.
modeless · 1d ago
Zapcc can, and frequently does, speed up the compilation of single files by 20x or more in the incremental case. CCache can't do that. And it's by far the common case when compiling iteratively during development. A speedup that large is transformational to your development workflow.
Too · 1d ago
The readme does a really bad job of explaining this. They are also using the wrong metrics. What’s really interesting is time to incrementally recompile after changing one header file in $LARGE_PROJECT. Single file compilation sounds like an artificial measurement and isn’t exactly painful with normal clang today. Full build measurements are rather irrelevant in a project with proper incremental setup, or even ccache. Not that I would say no to faster full builds :)
wakawaka28 · 1d ago
ccache speeds up compilation of single files by quite a lot, by effectively avoiding unnecessary recompilation. There are distributed caches that work like ccache too. Compiling a file for a second or fiftieth time with no changes because of doing a clean build is the absolute most common case. Maybe zapcc does additional caching of compiler internal state, but I would have to look into it to see if it's actually good enough. Also, ccache works with many compilers, whereas zapcc is its own compiler. That is a huge advantage.
account42 · 1d ago
ccache does not speed up compilation at all, in fact it slows it down. It only speeds up re-compilation of the same translation unit (as in, bitwise identical preprocessed source code) which is often not all that useful, especially when the local development rebuild use case is already covered by make.
wakawaka28 · 1d ago
ccache hardly slows anything down. It is a thin wrapper around running the compiler. You seem to kind of understand how it works, but it has multiple configurable ways to detect whether a file should be compiled. It does a LOT more than make, which does NOTHING to handle clean rebuilds or multiple builds of similar code in different locations. Unlike make, it does not rely on file timestamps alone to decide whether to rebuild an output.
jhasse · 1d ago
I have a cpp file that takes 10 seconds to compile. I change one line. How does ccache help me in this case?
wakawaka28 · 1d ago
When you do a clean build, it will pull that output from the cache if it has been compiled before and has not changed since. It does not technically speed up compilation of files. It bypasses compilation when it can.
jjmarr · 1d ago
ccache only caches the individual object files produced by compiling a .cpp file.
C++ build times are actually dominated by redundant parsing of headers included in multiple .cpp files. And also redundant template instantiations in different files. This redundancy still exists when using ccache.
By caching the individual language constructs, you eliminate the redundancy entirely.
account42 · 1d ago
Why should anyone eat apples instead of oranges?
ccache doesn't add anything over make for a single project build.
wakawaka28 · 1d ago
Yes, it does a LOT more than make. You can set it up to cache files for every copy of a project in your workspace. So if you kick off 10 related builds, for example, only the files that differ will be compiled twice. Although I guess it depends on what you mean by a "single project build". Even in the case of one copy, a clean rebuild will use the cache to speed things up.
FooBarWidget · 1d ago
zapcc looks really promising! Unfortunately it seems to be unmaintained: haven't seen any commits in 5 years.
TylerGlaiel · 1d ago
All that the C++ committee needed to do was just introduce "import" as "this is the same as include except no context can leak into it".
Would have been dirt simple to migrate existing codebases over to using it (find and replace include with import, mostly), and initial implementations of it on the compiler side could have been nearly identical to what's already there, while offering some easy space for optimizing it significantly.
Instead they wanted to make an entirely new thing that's impossible to retrofit into existing projects so its basically DOA
reactordev · 1d ago
That could be said of every additional c++ feature since c++0x.
The committee has taken backwards compatibility, backwards - refusing to introduce any nuance change in favor of a completely new modus operandi. Which never jives with existing ways of doing things because no one wants to fix that 20 year old codebase.
StilesCrisis · 1d ago
My experience has been that everyone _wants_ to fix the 20 year old codebase! But it’s hard to justify putting engineers on nebulous refactoring projects that don’t directly add value.
reactordev · 21h ago
This I understand. The risk of revenue loss if not done right, the risk of revenue loss if not done at all.
feelamee · 1d ago
if no one wants to fix this 20yo codebase, why is there someone who wants to push it to the new C++ standard?
Maken · 1d ago
The goal is to be able to import that 20yo battle-tested library into your C++20 codebase and it just works.
anon-3988 · 1d ago
Hyrum law would dictate that your C++20 now becomes C++11 or whatever is the oldest in the whole chain of dependency.
Maken · 1d ago
That's kind of the point of C++'s retro-compatibility: C++20 contains new additions to the C++11 standard. Unless you rely in mistakes in the standard like auto_ptr or faulty atomics, which should be fixed regardless of the C++ standard, your C++11 dependencies are perfectly valid C++20 and do not block the new shiny toys.
feelamee · 1d ago
there are really a lot of simpler solutions than switching the standard of the whole codebase.
E.g. write wrapper which interface doesn't require a new standard.
reactordev · 1d ago
Mmm pimpl’s and acne…
reactordev · 1d ago
You have it backwards. Everyone wants a new standard but no one wants to fix the code to make it work with a new way. They would rather introduce a whole new thing.
We want new stuff, but in order to do that we must break old stuff. Like breaking old habits, except this one will never die.
bluGill · 1d ago
we spent a billion dollars to rewrite our code breaking everything. 15 years latter and we have a better product than the old stuff- but 15 years ago was pre c++11, and rust didn't exist. i cannot honestly ask for another billion dollars to repeat that again and we are stuck. We must keep the old code running and if your new thing isn't compatible with whatever we did back then you are off the table.
c++26 still builds and runs that c++98 and so we can use it. Rust is nice but it can't interoperate as well in many ways and so it gets little use. you can call that bad desingn - I might even agree - but we are stuck.
reactordev · 1d ago
This. This is why C++ is stuck. The effort required to rewrite old shit code (that works, does its job, makes money) is just too valuable to the company so it sits. All vendors must conform or else be shown the door. No innovation can take place so long as Clu has its claws in our company.
I empathize. This is where rust could really help but there’s a lot of hate around “new” so it sits. The C++2042 standard will still have to be able to solve for C++98. The language will die. A pointer is a pointer and it shouldn’t need weak_ptr, shared_ptr, unique_ptr etc. If you expose it, it’s shared. If you don’t, then it could be unique but let the compiler decide. The issue with all these additions is they are opt in for a community that would rather opt out since it means learning/rewriting/refactoring.
I’ve come across this so many times in my career. Thank goodness for AI and LLMs that can quickly decompose these hairball code bases (just don’t ask it to add to it).
I love C/C++ but it’s so old at this point that no sane person should ever start with that.
StilesCrisis · 1d ago
If you think the problem with C++ is that shared_ptr exists, you should probably just use C.
reactordev · 23h ago
If that is what you take from it, you missed the point entirely.
I could care less about shared_ptr.
The issue is why should I care? Why is it on the dev to determine how a pointer should work? Why the dev has to go back and refactor old code to be new again? Why can’t the committee build non-breaking changes to the spec? I would rather have compile flags that make a pointer an “old pointer style” vs having to mentally juggle which ptr container to use, when, and why.
This is just one example. Gang of 3, becomes gang of 5, becomes mob of state…
It’s just a giant mess.
bluGill · 21h ago
that youieven think a compile flag could switch everything shows how little you understand the problem. it is fine not to understand - it isn't possible to understand everything - but stop talking as if there is an easy problem that would work.
account42 · 1d ago
No it can't be said for most other C++14+ features as they are actually implemented and used in real code bases.
reactordev · 1d ago
Touché, but why the 4 different pointer containers?
imtringued · 1d ago
What do you mean by "no context can leak into it"? Do you mean it shouldn't export transitive imports?
As in `#include <vector>` also performs `#include <iterator>` but `import vector` would only import vector, requiring you to `import iterator`, if you wanted to assign `vec.begin()` to a variable?
Or is it more like it shouldn't matter in which order you do an import and that preprocessor directives in an importing file shouldn't affect the imported file?
ninkendo · 1d ago
Not GP, but I take it to mean I can’t do:
#define private public
#import <iostream> // muahaha
Or any such nonsense. Nothing I define with the preprocessor before importing something should effect how that something is interpreted, which means not just #define’s, but import ordering too. (Importing a before b should be the same as importing b before a.) Probably tons of other minutiae, but “not leaking context into the import” is a pretty succinct way of putting it.
TylerGlaiel · 1d ago
yeah that, include is a textual replacement, so anything placed before the include is seen by all the code in the include. Not just other preprocessor stuff and pragmas but all of the other function definitions as well. There are some cases where this has legitimate use, but also is one of the main reasons why compilers can't just "compile the .h files separately and reuse that work whenever its included, automatically"
you define #import as "include but no context leaks into it" and that should on its own be enough to let the compiler just, compile that file once and reuse it wherever else its imported. That's like 95% of the benefit of what modules offered but much much simpler
StephenHerlihyy · 1d ago
Modules provide more than just speed. Compile time benefits are great and the article is right about build-time bloat being the bane of every developer. But modules serve a deeper purpose.
Explicit sub-unit encapsulation. True isolation. No more weird forward declarations, endlessly nested ifdef guards, or insane header dependency graphs. Things just exist as they are separate, atomic, deterministic and reliable.
Modules probably need a revision, and yes, adoption has been slow, but once you start using modules you will never go back. The clarity of explicitly declared interfaces and the freedom from header hell fundamentally changes how you think about organizing C++ code.
Start a new project with modules if you don’t believe me. Try them. That is the largest barrier right now - downstream use. They are not supported because they are not used and they are not used because they are not well supported. But once you use them you will start to eagerly await every new compiler release in hopes of them receiving the attention they deserve.
tsimionescu · 1d ago
Use a feature that doesn't really work, after so many years, hoping compilers will actually make it work if people use it - seems like a disastrous plan. People have tried to use modules, and have generally found that they fail, and have dumped them.
It's unlikely at this point modules in their current form will ever be anything but a legacy feature in C++. Maybe someday a new implementation will arise, just like noexcept replaced the failed "throws" declaration.
cyberax · 1d ago
Or a more relevant example: export templates.
zbendefy · 1d ago
Thing is (correct.me if Im wrong), that if you use modules, all of your code need to use modules (e.g. you cant have mixed #include <vector> and import <vector>; in your project). Which rules out a lot of 3rd party code you might want to depend on.
feelamee · 1d ago
you wrong
You can simply use modules with includes.
If you will #include vector inside your purview then you will just get a copy of the vector in each translation unit. Not good, but works. On the other hand. If you include a vector inside the global module fragment, then the number of definitions will be actually 1, even if you include it twice in different modules.
Kelteseth · 1d ago
Not sure why you are getting downvoted, but this alone would make me switch (Still waiting for Qt moc support):
> No more weird forward declarations
We c++ devs just have collectively accepted that this hack is still totally ok in the year 2025, just to improve build times.
imtringued · 1d ago
The only thing that forward declaration buys you is avoiding to include a header, which is not that expensive in C, but thanks to templates something as innocent as including a header can become infinitely expensive in C++.
thegrim33 · 1d ago
Forward declaration breaks dependency chains so that you don't need to recompile vast swathes of your codebase anytime an upstream header file is modified.
domenicd · 1d ago
The standardization process here feels similar to what happened with JavaScript modules. Introduced in ES2015, but the language standard only had syntax and some invariants. It had no notion of how to load modules, or how they might be delivered, or how a program that had a module at its root might be started. But there was a similar urgency, of "we must have this in ES2015".
I made it one of my first projects after joining the Chrome team to fix that gap, which we documented at [1]. (This reminds me of the article's "The only real way to get those done is to have a product owner...".)
You could even stretch the analogy to talk about how standard JS modules compete against the hacked-together solutions of AMD or CommonJS modules, similar to C++ modules competing against precompiled headers.
That said, the C++ modules problem seems worse than the JavaScript one. The technical design seems harder; JS's host/language separation seems cleaner than C++'s spec/compiler split. Perhaps most importantly, organizationally there was a clear place (the WHATWG) where all the browsers were willing to get together to work on a standard for JS module loading. Whereas it doesn't seem like there's as much of a framework for collaboration between C++ compiler writers.
I did C++ for over 10 years, and now have been doing rust for about 4. On the whole, I like rust much better, but I really miss header files.
Modules are horrible for build times. If you change an implementation (i.e something that would not normally involve editing a header) the amount of rebuilding that happens is crazy, compared to any C++ project that was set up with a minimal amount of care.
That change sounds like a big improvement if they manage to do it!
jjmarr · 1d ago
Changing the private module fragment (equivalent of a source file) shouldn't cause rebuilds since it doesn't change the interface.
WalterBright · 1d ago
D modules are very fast. Many of our customers rely on D being way faster than C++ to compile.
feelamee · 1d ago
I often hear about a lot of advantages of D. So I don't understand why it is so unpopular.
Probably I need to give it a chance, but I'm unsure that I will find a real job with the D stack.
dzdt · 1d ago
Most languages that have made it big have been the primary language for a platform that made it big, and it is really the features of the platform more than those of the language that have driven that.
tialaramex · 1d ago
> Most languages that have made it big have been the primary language for a platform that made it big,
I think you're going to want to define "made it big" and then make yourself some lists as this to me sounds like it doesn't have much explanatory power.
WalterBright · 1d ago
Knowing D will make you a better programmer in other languages. For example, D pushes you to write better encapsulated modules, which is a transferable skill.
emtel · 1d ago
How does D avoid the problem I described?
WalterBright · 1d ago
For one thing, it never has to compile a module more than once, no matter how many times it is imported. For another, the compilation of a module is done on demand. For a third, D modules can be "header only", omitting the implementation.
nitwit005 · 1d ago
While I can see compiler authors not wanting to have to turn the compiler into a build system, I'd really appreciate if they did do that. Having to create makefiles or other build artifacts is such a waste of energy for most applications.
Too · 1d ago
Shifting more of this to the compiler is logical, because import rules are defined by the language, not by the build system. In fact, today build systems function by asking the compiler to output a list of transitive includes, so it knows which files to rebuild when someone touches a header.
bluGill · 1d ago
Build systems are far more complex than anyone thinks. If your build system doesn't cover all the weird things we do it is not usable at all.
flohofwoe · 1d ago
But oth, nothing is better at dealing with edge cases and weird things than writing your build scripts in an actual programming language. All that an integrated C++ build system would do is add a new std::build API, and for the compiler to discover a build.cpp file, compile and run that to perform the build. All the interesting "build system stuff" stuff would be implemented as regular stdlib code.
See Zig for a working example of that idea (which also turned out to be a viable cmake replacement for C and C++ projects)
It could even be reduced to compiler vendors agreeing on a convention to compile and run a build.cpp file. Everything else could live in 3rd party code shipped with a project.
bluGill · 1d ago
Real build systems need to deal with any language. Cargo is a negative on large projects because it has opinions that make things easy for small rust only projects and falls flat on complex projects-
good build systems need to bea real programming language. The more declaritive the better - this is different from the project that often should be a different style
flohofwoe · 12h ago
As long as the build system can run external programs, you're really not limited to a single language (or even compiling source code). In the end build systems are just fancy task runners, no matter if they are integrated with a language toolchain or separate products.
bluGill · 7h ago
Sortof. If the build system doesn't understand the dependencies you have inefficient builds as it can't schedule all cores effectively at all times. On a large complex project this matters
nitwit005 · 1d ago
You can just fall back to some other tool in a complex case. That's what people do with a number of other languages.
psyclobe · 1d ago
Modules are unusable in real projects with real dependencies. Until that changes there’s no chance I’ll look at them.
feelamee · 1d ago
how are modules related to dependencies in general? You can use your modules at the same time using dependencies via includes. And this works well.
psyclobe · 22h ago
Can't include _any_ header downstream if you import std, it is also unknown how you're gonna export and share modules across dependencies you have no indention of 'porting' to modules...
casualrandomcom · 1d ago
"You might want to grab a cup of $beverage before continuing, this is going to take a while."
Instead, I launched a compilation.
ch33zer · 1d ago
My belief is that modules were designed for big tech companies that do massive builds parallelized across thousands of machines. Such an environment makes the complexity bearable and you get massive speedups from cached build artifacts. I think smaller companies and hobbyists kinda got screwed.
Kelteseth · 1d ago
Where does this belief come from? For that you can already use Mozillas sccache.
monkeyelite · 1d ago
I absolutely love having headers in C and stronger notions of compilation units. But in C++ you tend to want to push everything into headers to take advantage of generics. This is another problem where a separate code generation step would have been superior to language generics
whatsakandr · 1d ago
He makes such a good point. This is such a tragedy. Import std would of been an amazing thing that would of led some much less pain, but the committee got greedy.
forrestthewoods · 1d ago
> modules should be killed and taken out of the standard.
Yes. The C++ standard is an abomination that is both over-specified and wildly under-specified.
Modules are simply never going to work en masse. It is a failed idea.
jmull · 1d ago
> the "header inclusion" way is an O(N²) algorithm
Maybe I don't know what they mean by this, but the header inclusion way is O(N*M), is it not? Where N is the number of source files and M is the average number of dependencies each has (as expressed as #includes).
If M is approaching N -- meaning everything depends on everything -- you probably have bigger problems than compile times.
That's also setting aside precompiled headers. Those wouldn't be N^2 either, even when the precompiled header needs to be precompiled.
kookamamie · 1d ago
Fully agreed. The modules are an afterthought, and inevitably lead to a mess where we have both modules and includes all over the place.
pizlonator · 1d ago
The problem with C++ modules is that they are empirically inferior to headers and separate compilation units.
The pre processor and linker, as derided as they are, allow for scaling of software size to extremes not possible with supposedly superior languages’ supposedly superior module systems.
Want to build a >billion line of code OS? You can do that with headers and separate compilation units. Good luck doing that with any other tech
nitwit005 · 1d ago
The Linux kernel is tens of millions of lines, so I'm rather doubtful at the existence of this OS over a billion lines.
uncircle · 1d ago
> I'm rather doubtful at the existence of this OS over a billion lines
Someone somewhere is trying to vibe code the entirety of the Linux kernel using Copilot.
pizlonator · 1d ago
That's just the kernel. An OS is more than the kernel
saagarjha · 1d ago
I don't see anything that makes this impossible. In fact I think this is just a result of nobody making a project of that scale in those languages yet, rather than it being fundamentally impossible.
pizlonator · 1d ago
It doesn't matter if you or I see what makes it impossible.
It's a fact that:
- Super large software tends to be C or C++
- One of the unique features of C and C++ is the way software is organized (headers and separate compilation).
- Attempts to bring other languages' approach to modules to C++ have been a complete disaster.
Hence it's an empirical fact that C and C++ have a superior approach to software scaling, and that approach is headers and separate comp. And a preprocessor with macros.
aw1621107 · 23h ago
> Hence it's an empirical fact that C and C++ have a superior approach to software scaling
I'm not convinced the "superior" follows. What I'm missing is a causal link between your first and second points - super large software could have been written in C or C++ for reasons other than the header/implementation split (especially if the super large software wasn't so super large at first and grew into its size instead of being planned for that size from the start), and somewhat conversely programming languages with header/implementation splits don't necessarily have corresponding super large software (e.g., OCaml, at least publicly?).
aw1621107 · 1d ago
> The problem with C++ modules is that they are empirically inferior to headers and separate compilation units.
I didn't think modules and separate compilation units are (completely) mutually exclusive? It's not like modules force you to use a single compilation unit for all your code; it just changes where the compilation unit boundaries/dependencies are (though to be fair I'm not entirely certain how much heavy lifting that "just" is doing)
> Want to build a >billion line of code OS? You can do that with headers and separate compilation units. Good luck doing that with any other tech
It's not immediately obvious to me why this must be the case. That's the fundamental limitation of modules systems that supposedly prevents this scaling?
TuxSH · 1d ago
> That's the fundamental limitation of modules systems that supposedly prevents this scaling?
Not the person you're replying to but I can see a problem with some dependency chains.
Let's say you have: stdlib <- A.hpp <- B.hpp (small header) <- C.hpp (likewise) <- many .cpp files.
If you only precompile A.hpp (as is commonly done), the many .cpp files can be compiled in parallel once A.hpp is precompiled, and you get a nice speedup.
If on the other hand you need to precompile everything, then all these cpp files must wait on A, then B, then C to be precompiled
aw1621107 · 23h ago
I'm not entirely sure modules systems must face that limitation. C++'s module system, for example, permits separation of module interfaces and module implementations, much like the existing header/implementation system. IIRC OCaml's module system does something similar, though I'm not familiar enough with it to say whether it qualifies as a module system beyond the name.
Speaking more abstractly even if there isn't an explicit interface/implementation separation perhaps compilers could pick out and make available interface information "ahead of time" to alleviate/possibly eliminate the effect of otherwise problematic dependency chains? For example, in Rust you "just" need to look for signatures to figure out the interface and you have the corresponding keywords to make searching for those easy without having to fully parse the entire file.
There's also the question of whether super large projects must have problematic dependency chains - hypothetically, if a super large project were written in a language with a module system some work would be done to try to structure it to minimize build time/system pain points. I don't think I can confidently say that that is always (im)possible.
TuxSH · 5h ago
> Speaking more abstractly even if there isn't an explicit interface/implementation separation perhaps compilers could pick out and make available interface information "ahead of time" to alleviate/possibly eliminate the effect of otherwise problematic dependency chains?
I'm not sure how well this would work for non-instantiated templates
> There's also the question of whether super large projects must have problematic dependency chains
Any header precompilation dependency chain is a dependency chain and may end up worse than fully parallel TU compilation if the time to parse said headers is faster than the time to compile them in a serial way.
I can see modules being used, but relegated to, "import std; import fmt; import vulkan (etc)", typically use cases one should already use PCH for.
nurettin · 1d ago
If you avoid circular refs and forward declarations by writing hierarchical code, you won't really need modules. In my current project I'm 20+ headers in and still haven't had a circular ref. Just refactor all the commonly used code, decouple with callbacks and you have yourself a nice clean header only library that you can amalgamate and/or precompile.
RossBencina · 1d ago
> If you avoid circular refs
See the book "Large Scale C++ Software Design" by John Lakos for more detail in this direction.
WhereIsTheTruth · 1d ago
They had an opportunity to finally get rid of requiring forward declaration, but they failed..
Why bother? The world seems to have moved on to Rust, C++ is only for legacy maintenance stuff anymore.
okanat · 1d ago
Newer stuff yes and it is great. However from basic rendering to browsers and the most complex applications (CAD, office software, finance, complex solvers like airline planning) are still in C++. Nobody will accept rewriting 35+ years of history.
C++ code bases are really a lot longer-lived than any other software builds upon them. Hence we cannot drop it.
Starting with C++17, I think committee has been doing the language a disservice and piled even more and more unintended complexity by rushing "improvements" like modules.
I don't write C++ anymore due to my team switching to Rust and C only (for really old stuff and ABI). I am really scared of having to return though. Not because I am spoiled by Rust (I am though), but because catching up with all the silly things they added on top and how they interact with earlier stuff like iterators. C++ is a perfect language for unnecessary pitfalls. Newer standards just exploded this complexity.
12%. Assume the progress is linear (not logarithmic like most cases), we just need 60 more years to migrate those c/c++ code.
ra1231963 · 1d ago
That was my point — with LLMs the progress would not be at the same slope as with people only.
flohofwoe · 1d ago
Have there been any successful attempts yet of translating 'idiomatic' C++ to 'idiomatic' Rust for a large codebase that has been developed over 30 years? What does the output look like? Does the code look maintainable (because mechanical solutions to translate from other languages into Rust exist, the result is just not what a human would write or ever want to work on). Are the prompts to guide the LLM shorter than the actual code base to be translated? Etc etc... eg the idea to use LLMs for migration is quite 'obvious', but the devil must be in the details, otherwise everybody would be doing it by now.
okanat · 1d ago
Idiomatic C++ allows too much "freestyle" and duck-typed code. Rust language basically doesn't support half of the things that C++ allows because there is no type or memory safe ways to achieve them. When translating things into Rust, borrow checker forces you to invert the logic or completely redo the architecture. Oftentimes it requires trying multiple things and evaluating the performance cost, generated machine code quality and interface flexibility. It is quite difficult without an actual person to translate things.
flohofwoe · 1d ago
Yeah that's why I'm very sceptical about any 'LLM will magically fix all the things' claims. The mechanical translations basically work by compiling the source language down to LLVM bitcode and then translating that back to another language, it works but is basically assembly code written in a highlevel language, it loses all 'semantics' and is entirely unmaintainable.
c0balt · 1d ago
I'm pretty sure a lot of fields would disagree with you. Last I checked game programming, OS development, embedded development and more were deeply invested in either C or more often C++, especially when RT tasks were involved or vendor-provided compilers are required.
Rust just doesn't have close to the same type of adoption/support yet, especially when considering various embedded platforms.
holowoodman · 1d ago
There is a big big difference between C and C++. The article is about C++, which is being replaced by Rust imho. C is different, and far more frequently used for embedded or OS development. Don't know about games, but last I looked, stuff like unity was C#, so something else yet again.
grg0 · 1d ago
Game engines (the thing that C# runs on top of) are written in C++. As is the C#/.NET runtime. As is anything that requires careful management of memory for performance. Application code is perfectly reasonably written in managed languages, but not so for the things that run underneath.
ShinTakuya · 1d ago
There's nothing inherent about C++ that makes it more suited than Rust for game engines though, Rust supports careful management of memory too. Of course, nothing besides inertia (i.e. Libs, existing code, etc.). And that of course is more than big enough of a reason to stick with it.
grg0 · 1d ago
Rust supports careful management of memory at the expense of unsafety, at which point that particular area of the code offers no benefit over C/C++, let alone value in a rewrite. There is no type system for reinterpreting bits of memory.
And for the safe parts, the posts that I've read from people who have spent a non-trivial amount of effort with the language do not paint a clear picture either on whether there was really a benefit to the language overall.
So to say that "the world has moved on" in light of all of this is pure hubris.
jonathrg · 1d ago
C++ is widely used in embedded. Most compilers support it. Usually you turn some things off (e.g. -fno-rtti, -fno-exceptions) and try to stick to some sane subset of the ++.
com2kid · 1d ago
C++ in embedded is basically C with classes. It is a nice simple language and often doesn't even include templates (or if it does they are very simple data structures that are not even heap allocated).
C++ as C with classes is a pretty good language!
No comments yet
feelamee · 1d ago
haha, fun fact - Unity is C++ code base. You need to look deeper
drwu · 1d ago
It is a pitty that
- GCC switched from C to C++
- CUDA switched from C to C++
But I can understand the decision, and at that time , C++ frontend features and libs were a little bit less horrible.
deeznuttynutz · 1d ago
It's a pity?
Please explain in detail how alternatives would have worked better for GCC and CUDA. Also, if you could share some historical context about how those alternatives could realistically have been implemented at the time, that would be helpful too.
I love to hear all the "would've" and "should've" scenarios.
j16sdiz · 1d ago
Last time I checked, MSVC don't want to implement C99/newer and instead focus on C++.
drwu · 1d ago
To be fair, MSVC has the most C99 stuffs. What is mainly missing for porting (my) programs is the native complex number. But we have Intel compiler for free on Windows, which is fully compatible with the C/C++ standard and produces faster binaries.
The C++ frontend of MSVC handles (the common) compiler-specific language extensions differently than the other compilers. Besides, its pre-processor behaves differently too. It is now good that there is a clang frontend for MSVC.
spacechild1 · 1d ago
How did you get that impression? There are many areas where C++ is still the first choice. Also, "legacy maintenance stuff" is quite an understatement, given how much of our world is powered by C++: browser engines, language runtimes, game engines, video editors, DAWs, automation software, medical equipment, flight control software, etc.
If you think that "the world seems to have moved on to Rust", I would recommend to look at the job listings. For example, devjobs.de: Rust: 61, C++: 1546. That's 1:25. Maybe in other countries there are more Rust jobs?
diath · 1d ago
There are a number of areas in programming where I'd always choose C++ over Rust - gameplay programming, retained-mode GUI programming and interpreted programming languages to name a few have very complex circular memory models that are somewhat solvable with weak_ptrs or refs stored in member variables passed through constructors but would be absolutely obnoxious to deal with and get right with the borrow checker.
ozars · 1d ago
Rust has Arc and Weak that can be used for solving it in a similar way C++ does. The primary difference is forcing usage of Mutex to avoid a large class of data race issues IMHO, and that's what makes it harder to get right. Async in Rust is also more mature than C++ coroutines. So there's also that.
Arc/Weak can only get you so far. The problem I have with Rust in the mentioned domains (gameplay programming, retained GUI, interpreters) is what makes it so good, sharing xor mutability. If you're writing lots of multi-threaded code it's fantastic. If your problem domain is like the ones mentioned it hurts you.
These are all highly non-parallel problems. They don't gain much from being parallel, and because Rust imposes 'restrict' semantics on even single threaded code you end up making it much harder to write code in these domains.
This has been my experience with Rust. Shared mutability is safe on a single thread without 'restrict' on all your pointers, and Rust has limited options to opt into shared mutability (with lots of ergonomic caveats).
Don't get me wrong though, I still think Rust is a great tool. It just has tradeoffs.
zozbot234 · 1d ago
> Rust has limited options to opt into shared mutability
The idiomatic Rust equivalent of a C non-restrict pointer is arguably &Cell<T>. The biggest problem with it is library code that takes &mut T when the likes of &Cell<T> might suffice (because potential aliasing does not affect the semantics of what that Rust code is doing), but this is an acknowledged problem and the Rust project will take pull req's that fix it where it occurs.
ozars · 1d ago
Shared mutability can cause race conditions in single-threaded environments as well, if you need asynchronous contexts where mutations can be interleaved between suspension points. Think of events, message passing channels, I/O, timers, ticks...
If you're sure you're never going to need multi-threaded environment, you have an option as well: Replace std::sync with std::rc, Mutex with RefCell in the above toy example and that's about it.
If you want to use some asynchronous runtime, replace std::sync with tokio::sync (or std::rc), slap async/awaits along with a single-threaded runtime and that's about it.
Of course, the code above is just a toy example and business logic is much more complex in real world, but compare this to what it would take to write same C++ logic in async.
I found Rust's approach massively more ergonomic compared to C++ approach of passing closures around for asio-like IO contexts or coroutine compiler-magic which opens new novel avenues to shoot myself on the foot, well, to the extent I could grasp it.
It's true Rust forces you to pay all this cost ahead of time. It's also true most applications don't require this level of safety really, so it becomes ridiculous to pay it upfront. And even for some that require such high level of safety, you can skip bunch of bolts on a plane door and it will still be a billion dollar company at the end of the day, so...
lenkite · 1d ago
Rust doesn't have computed goto (gcc/clang extension heavily used in interpreters like CPython and LuaJIT for direct threading).
You’re forced into function-call or jump-table dispatch, which tends to be slower.
aw1621107 · 1d ago
> Rust doesn't have computed goto (gcc/clang extension heavily used in interpreters like CPython and LuaJIT for direct threading).
On the other hand, there's the recently-added-to-nightly `become` for guaranteed tail calls, which might work better than computed gotos if CPython is a good example [0]
> When using both attributes [[[clang::musttail]] and preserve_none], Jin's new tail-call-based interpreter inherits the nice performance benefits of the computed-goto-based version, while also making it easier for the compiler to figure out optimal register allocations and other local optimizations.
To be fair I don't think Rust has a preserve_none equivalent yet, but given naked functions are a thing in Rust I'd hope it isn't too bad to support?
There isn't much reason to use Rust if you just bypass the borrow checker.
nurettin · 1d ago
Yes, you could just use Arc everywhere. But then you've spent a large portion of your coding time fighting the compiler and wasting lines for something that will essentially always be single threaded. (a UI event loop)
The C++ solution would be to start the threads and use an MPSC queue (which, ironically, Rust also has) in order to update the UI.
Rust will eventually stumble upon ergonomics and allow portions of code to be specified as single threaded or embarrassingly parallel, but unfortunately the community evolved horse blinders early on and isn't letting them go any time soon.
flohofwoe · 1d ago
Rust isn't exactly famous for its fast build times. It's probably the only language that actually builds slower than C++, which is an impressive achievement on its own ;)
And give Rust one or two more decades and it will be the same messy kitchen sink language as C++. If anything, Rust is speedrunning C++ history (with the notable exception of fixing static memory safety of course).
whatevaa · 1d ago
Borrow checker is actually not very good solution for some domains. I remember reading that it would not be good for writing javascript VM like v8. Something about managing memory which lifetime actually depends on others, not rust code.
InCom-0 · 1d ago
Have a look at any serious job postings.
C++ jobs outnumber Rust jobs somewhere around 50:1.
Internet hype meets actual industry reality :-).
tandr · 1d ago
Or could it be (or to be a bit of devil's advocate) that Rust projects require only 2% (1/50) of manpower, comparing the same project using C++?
Dylan16807 · 1d ago
That's not devil's advocate, that's just being silly.
SkiFire13 · 1d ago
Even if the basic idea might make sense there's no way the number if anywhere close to 1/50. Even 1/2 sounds unrealistic.
lyu07282 · 1d ago
I couldn't even tell if you are joking. Rust is weird, its like a programming language people developed a parasocial relationship with. If a Twitch streamer was a programming language it would be Rust. Perhaps its just people being immature I don't know.
tandr · 1d ago
I was, but I guess adding /s at the end in these cases is a requirement nowadays...
feelamee · 1d ago
> I couldn't even tell if you are joking
I'm thinking the same about your comment :D
lyu07282 · 1d ago
It's a programming language with a fandom, isn't that odd? ^^
fooker · 1d ago
C++ has a habit of incorporating all the best ideas from it's 'succcessor' languages once these ideas are mature.
Philpax · 1d ago
A lot of the problems with C++ are more foundational; you can't adopt the changes that newer languages have made, because that would be a new language - and we know this, because that new language's name is Carbon.
There are things you can add, but the rot still permeates the foundations, and much of the newness goes partially unused because they're just not at home in C++. Use of `std::optional` and `std::variant` is, as far as I know, still limited, even in newer C++ code, because the ergonomics just aren't there.
fooker · 1d ago
optional is heavily used in new codebases.
variant isn't, yet. We'll eventually get some kind of structural pattern matching that will make variant or it's successor more idiomatic.
C++ does have quite a bit of rot, you're right. But that's the price of building technology people actually use.
Carbon doesn't seem to have any fundamentally new ideas, we'll see how well it fares in the wild.
ghosty141 · 1d ago
> variant isn't, yet. We'll eventually get some kind of structural pattern matching that will make variant or it's successor more idiomatic.
Thats the fantastic thing about c++, you can already write an easy to use match, but they just chose not to include that in the stdlib but rather want you write that yourself.
Also optional and expected are ergonomic nightmares since there is no "try!" macro like rust has. In general it lacks the infrastructure that is needed to make these types nice to use. It also clashes with other concepts like RAII where you kinda have to use exceptions when it comes to ctors that may fail.
fooker · 1d ago
It would be pretty ugly to make this more introspective in C++.
For example, if you had to match patterns to distinguish between these three possibilities when looking at a tree: (val), (val (tree left) (tree right)), (val (tree subtree)). Here, the possibilities are not really separate types, but rather different ways of constructing a type. This sort of pattern shows up in general purpose programming (even meat and potato imperative programming) pretty often.
The try! macro was deprecated in 2019 so, almost six years ago. And even before formal deprecation the word try is now a keyword and so you'd need to use r#try! to say you want the obsolete macro not the keyword.
account42 · 1d ago
Ergonomics of std::optional and std::variant have nothing to do with bad foundations and all to do with the C++ committee insisting on these being library features rather than language features. I'm sure we will get the syntactic sugar eventually like we did get range-based for loops after having had to deal with macro approximations of foreach forever.
fooker · 1d ago
You’re right.
I have some hope that the upcoming compile time reflection will make it easier to implement said syntactic sugar.
ReflectedImage · 1d ago
Rust is basically the wanted fixes to C++ that C++ itself could never adopt for legacy reasons.
So I'm afraid no by definition C++ can't adopt Rust's ideas because Rust's ideas were originally impossible C++ ideas.
fooker · 1d ago
> C++ itself could never adopt for legacy reasons.
I agree with your point except for the 'never' qualifier. It was certainly true when Rust was born.
C++ has proven the 'never' part wrong multiple times. I think, by 2030, the only thing that C++ would lack that Rust has right now is the unified toolchain/packaging ecosystem because people are not going to settle that debate.
Everything else is well on its way to be implemented, in one of three forms - core language features (eg: concepts), language features that primarily enable writing more powerful libraries so that you do not have to come up with language features for everything (eg: reflection), and finally tooling support from the compiler as a test bed of what the language could guarantee (lifetime checks and annotations in clang).
Of course Rust is innovating pretty well too, I am very interested in seeing what async/coroutines are going to look like in a few years.
ninkendo · 1d ago
C++ can never change the way move vs copy semantics work, which is precisely the opposite of Rust’s. It’s the basis for rust’s ownership model, and it’s no small difference. Without this I don’t see how C++ can ever get a workable ownership model like Rust’s.
fooker · 1d ago
C++ just gives you the option to have it both ways.
You are free to design your library in a way that your users only see one of these.
tialaramex · 1d ago
What developers actually wanted, even years back when C++ didn't have anything named "move" was the destructive move semantic. It's an elegant, easy to understand feature for a language. I like it very much in Rust which has it.
C++ didn't get that. The proposal paper at the time says it's impossible (for C++). But what they did propose was the feature you've seen in C++ today, which they call "move", but it has slightly odd (though convenient to implement, Worse Is Better after all) behaviour.
Now you can make this C++ "move" behaviour out of destructive move, that behaviour is roughly what Rust calls std::mem::take and it's sometimes useful, which is why that function is provided. But, often it's not really what you wanted, and if you actually wanted destructive move but only have this C++ imposter then you need to perform the entire take, then throw away the newly created object. You will find lots of C++ code doing exactly that.
So, no, you can't "design your library" to deliver the desirable property in C++. It's just another of the dozens of nagging pains because of design mistakes C++ won't fix.
fooker · 1d ago
I am still confused about what you are claiming to be not possible in C++ when you are designing your library.
Yes, I understand that there’s a lot of bad code out there and C++ happily enables that. But that was not my point.
ninkendo · 22h ago
What’s not possible in C++ is the actual prevention of the moved-from value from being used after the move.
Entire API’s are designed around this: my type that tracks a background task can have a shutdown() method that moves self, so that if you call foo.shutdown(), you can’t use foo any more.
This is more than just preventing a value from being used, it also facilitates making a rule that “moves are only just memcpy()”, and it can actually be enforced:
C++ move semantics require you to write arbitrary code that pillages the moved-from value (like setting the heap pointer of a moved-from value to nullptr) to ensure the old value is safe: rust just says “nope, there’s no constructor, moves are just a memcpy. We will keep it safe by simply not letting code use the old value.”
C++ can never have this unless they offered yet another mutually incompatible form of move semantics (lol, what would the sigil be? &*&?)
tialaramex · 1d ago
Maybe it will be clearer if you read and understand my favourite function from Rust's standard library (its full name is core::mem::drop)
Also, you don’t really need this because of RAII. You can make it simpler, and here’s how you’d do this in C++26.
template <std::movable T>
void drop(T &&) {}
It can get even simpler!
void drop(std::movable auto){}
Do you see my point about C++ incorporating the good ideas at a glacial pace?
tialaramex · 22h ago
Edited to inject: Actually on second thoughts maybe none of these even do what you thought they did, they're even sillier than I'd assumed.
So funny thing, your C++ 26 solution doesn't do what my Rust function does.
Actually even the first one doesn't, but you likely don't really care about a std::unique_ptr, so it doesn't feel like a difference, and the negligence only really bites when it's not "just" that pointer type.
You see that Rust function destroyed the T. It's gone, no more T. But your C++ function makes a new T, then destroys the old one, so there's still a T, it's not gone after all.
fooker · 18h ago
> You see that Rust function destroyed the T. It's gone, no more T. But your C++ function makes a new T, then destroys the old one, so there's still a T, it's not gone after all.
Perhaps your idea of C++ semantics is a bit off?
tialaramex · 13h ago
After more consideration I think probably your functions don't do anything at all?
Is that the joke here? That despite everything you didn't understand why core::mem::drop has that definition and so reading the empty body you assumed that you can just not do anything and that'll work in C++ ?
fooker · 6h ago
Are you trolling? Or do you genuinely not understand why it might work in C++?
In your unique_ptr<T> example what you'd hidden (from me? Or perhaps from yourself) was that we're not destroying the unique_ptr, we're just destroying the Foo, and since the unique_ptr is null the destructor for that will be silent when the scope ends.
So what happens for objects that aren't behind a unique_ptr?
> Also, you don’t really need this because of RAII.
drop() indeed isn't used much because of RAII, but it is handy for those instances where you do actually want to dispose of something "early".
fooker · 23h ago
See the other versions I added.
> dispose of something "early".
This is a bit of an anti pattern in C++, but doable of course.
ninkendo · 21h ago
It’s not just for disposing things, it’s also used to decompose things into their constituent parts… like if I had something representing an HTTP response and it contains headers and a body, I could write a `fn into_parts(self) -> (Headers, Body)` that returns the parts you care about while destroying the response object.
This is useful in the grpc library I use, which has a response type with some metadata about the response, and an “inner” value that represents the thing the grpc method returned. If you just want the inner thing and don’t care about the metadata, there’s a `fn into_inner(self)` that destroys the response and leaves you the inner value.
You can write similar methods in C++ but they require leaving the original value in an “empty” state so that you can avoid expensive copying of things while still letting the moved-from value be “safe” to use. But that affects your API design… you now have to make the methods that fetch the “inner” thing be nullable/optional so that you can represent “oh this was moved from so there’s no body any more”. You don’t have to do that in Rust: moves are just a memcpy and the compiler will reject programs that use the moved-from value.
fooker · 6h ago
> compiler will reject programs that use the moved-from value.
Yes, this is something the C++ 'language' is not going to specify other than claiming that it is undefined behavior.
Doesn't prevent compilers from doing it though, clang will happily do this for you right now in most cases.
But it's not undefined behavior. That's the key. It's "unspecified behavior", meaning that according to the standard, it's allowed. A program that reuses a moved-from value is considered valid and well-formed by the standard. It merely delegates how to make this work to the individual implementation (a string becomes an empty string, a vector becomes an empty vector, etc.)
The RFC you posted has nothing to do with move semantics, it's about references outliving what they point to (ie. use-after-free, etc) and similar to Rust's borrow checker.
But here's the thing: move semantics and the borrow checker have nothing to do with each other! The borrow checker ensures that borrowed data (ie. &Foo, equivalent to C++'s references) is sound, it's not the part that enforces move semantics. That happens earlier in the compilation, the compiler enforces moves well before the borrow checker phase.
aw1621107 · 21h ago
> See the other versions I added.
I don't think those work either. Not only do neither of those actually end the lifetime of what's passed in, but they have other flaws as well.
> template <std::movable T> void drop(T &&) {}
This literally does nothing. Reference parameters don't affect what's passed in on their own - you need at least something on the other end (e.g., a move constructor) to do anything.
For example, consider how this would be instantiated for std::vector<int>:
template <> void drop(std::vector<int>&&) {}
> void drop(std::movable auto){}
I believe this is equivalent to passing by value, so this will actually invoke a copy if what's passed in isn't eligible for a move (or if the move constructor is equivalent to the copy constructor, since copyable subsumes movable). Again, consider how this would be instantiated for std::vector<int>:
template <> void drop(std::vector<int>) {}
> > dispose of something "early".
> This is a bit of an anti pattern in C++, but doable of course.
I don't think you can do quite the same thing in C++ without introducing new scopes.
fooker · 6h ago
> Not only do neither of those actually end the lifetime of what's passed in
>For example, consider how this would be instantiated for std::vector<int>:
I'm not sure why you keep posting snippets demonstrating C++ rvalue references. We all know what those are, and it's not what we're talking about.
We're talking about how the rust compiler uses move semantics to prevent you from using the moved-from value, such that code like this will not compile:
let f = Foo::new();
drop(f);
f.foo(); // Error: use of moved value
C++'s move semantics do not prevent you from using f after you've moved it. On the contrary, it's intentionally allowed. It's not undefined behavior either, it's "unspecified" behavior, which means "behavior, for a well-formed program construct and correct data, that depends on the implementation". This simply means that it's up to the individual type to decide what happens when a value is moved from. (A string becomes an empty string, for instance, or a vector becomes and empty vector.)
Rust's move semantics mean:
- You don't write a move constructor
- Moves are just memcpy
- The compiler enforces the old value can't be used any more
C++'s move semantics mean:
- You must write a move constructor (rvalue reference constructor)
- Moves are arbitrary code
- The language explicitly allows the moved-from value to continue to be used
That there are certain linter-style tools like clang-tidy which can be configured to warn on using moved-from values is irrelevant: The standard explicitly allows it. It's personal preference whether you should make a habit of using moved-from values in your codebase, which is why this will only ever be a linter thing. The C++ standard would have to completely change its mind and retcon moves to mean something different, if they ever wanted to change this.
Now, the beginning of this thread was someone saying "Rust is basically the wanted fixes to C++ that C++ itself could never adopt for legacy reasons". Then you came back with "I agree with your point except for the 'never' qualifier", implying C++ will eventually support Rust's ideas. But move semantics in C++ are precisely the opposite of those in Rust, because rust-style semantics were deemed impossible to implement in C++, even though it's what people actually wanted at the time. So I think it's fair to say C++ will "never" get Rust-style move semantics.
tialaramex · 1d ago
Concepts is a particularly hilarious example because what you got in C++ 20 is Bjarne's "Concepts Lite". Bjarne worked quite hard to get rid of the ideas and people behind the much more powerful C++ 0x Concepts, which resembles Rust's trait feature, and then it took a decade to land his worse alternative. That actually is a microcosmic survey of the problem with the language.
fooker · 1d ago
So what you’re saying is that it takes time, but works out?
I agree.
aw1621107 · 23h ago
> So what you’re saying is that it takes time, but works out?
Probably depends on what you mean by "works out". I don't think GP would agree that delivering a less capable alternative qualifies.
For example, one major feature C++0x concepts was supposed to have but got removed was definition-time checking - i.e., checking that your template only used capabilities promised by the concepts it uses, so if you defined a template with concepts you could be assured that if the definition compiled it'd work with all types that satisfied the concept. That feature did not make it to C++20 concepts and as far as I know there are no plans on the horizon to add that feature.
fooker · 6h ago
C++0x was ~5 years ago.
C++26 concepts has more or less everything you mention, and you can try it out with all the major compilers right now.
tialaramex · 4h ago
First lets clear up a thing I've seen a few times on HN probably from people who are new enough to simply not have run into this before.
C++ 0x is what people called the proposed new C++ language standard from about 2005 through 2009 or so under the belief that maybe it would ship in 2008 or 2009. Because you're here, now, you know this didn't end up happening and actually the next standard would be C++ 11. For a little while they even jokingly talked about C++ 0A where A is of course hexadecimal for ten, but by the time it was clear it wouldn't even make 2010 that wasn't funny.
So C++ 0x isn't five years ago, it's about 15-20 years ago and in this context it's about the draft revision of C++ in which for some time the Concepts feature existed, but Bjarne insisted that this feature (which remember is roughly Rust's traits) was not implementable in reasonable time, and frankly was not as much needed as people had believed.
This argument swayed enough committee members that Concepts was ripped back out of the draft document, and so C++ 11 does not have Concepts of any sort. Because this particular history is from the relatively recent past you can go read the proposal documents, there might even be Youtube videos about it.
OK, so, now you at least know what these terms mean when other people use them, that can't hurt.
As to your next claim er, no, not even close. Barry Revzin wrote a really nice paper connecting the dots on this, which probably passed into legend specifically for saying hey C++ 0x Concepts are the same thing as Rust traits. C++ proposal paper P2279 is what you're looking for if that interests you. That'll be less confusing for you now because you know what "C++ 0x" even means.
Now, Barry wrote that paper in the C++ 23 cycle, and we're now at / just past the end of the C++ 26 cycle, but I assure you that nothing relevant has changed. You can't magically have model checking in C++ that's not there. You can't provide concept maps, it's not in the language and so on.
flohofwoe · 1d ago
Rust has been copying plenty of bad features from C++ too though.
fooker · 1d ago
I think this is the trap that turned away many people from D.
flohofwoe · 1d ago
...and unfortunately butchering those good features beyond recognition, see C++20 vs C99 designated init. Typical "design by committee" outcome though :/
fooker · 6h ago
Agreed. Design by committee has been the thorn for a long time, hence efforts like Carbon, Circle, and cpp2.
But as it stands, I expect that whatever innovations these languages produce will be picked up by C++ in ~5 years.
boppo1 · 1d ago
Sarcasm?
grg0 · 1d ago
These comments only reflect the person's narrow view of the "world", along with a generous dosage of pride to state things about which one does not know. If every OS and driver and much of the backbone of society is "legacy maintenance stuff", then the statement is certainly correct.
bukotron · 1d ago
Rust does not solve any problem existing in expecienced C++ developer career. You dont write modern C++ code in such a way memory leak or dangling pointer is possible at all)
This is exactly WHY we dont see a rush movement of C++ developers to Rust throwing away everything for Rust. Rust is trying to solve problems that already not exist 99.9999% of time in modern C++ code style and standards.
Also, some day C++ compilers or tooling will get its own Borrow Checker to completely forget about Rust - this will be done just for fun just to stop arguing with rust-fans :)
okanat · 1d ago
This is a very bad take. Rust does solve very real problems in C family languages.
The number of people I met in Rust conferences that rewriting at least parts of rather big C++ codebases weren't small either.
However, there is still big amount of code that is purely C++. Many of the older code bases still use C++03-style code too. Or they were written in the OOP design pattern golden era that requires huge reactors to adapt functional / modern code. Anything with Qt will not benefit from smart pointers. Even with Qt 6.
Rust cannot solve these problems since the challenges are not purely technical but social too.
r_lee · 1d ago
I would say though that Rust has already had a profound social effect which has probably enabled those rewrites etc. It wasn't too long ago that it was brushed aside as noise yet now its gaining real momentum
Philpax · 1d ago
Thinking that experience with C++'s many flaws will save you from running into them is delusional - just look at the number of CVEs in projects maintained by world-class C++ programmers.
No amount of fallible human vigilance will stop you from forgetting the existence of a C++ quirk in the code you're rushing out before heading out for the night. Human oversight does not scale.
InCom-0 · 1d ago
This is true ... except that Rust doesn't actually do any better in that regard.
Rust solves 1 category of problems in a way that is not without its costs and other consequences. That is it. There are projects where this is very important, there are other projects where its virtually useless and the consequences just get in the way. It is not magic. It doesn't make anything actually 'safe'.
feelamee · 1d ago
sure, but don't forget that rust gives us also a nice tooling, functional syntax sugar like pattern matching, enums, monads; and other more or less useful things like explicit lifetimes
InCom-0 · 1d ago
Of course.
This is kind of to be expected as it has the benefit of hindsight of 25+ years. It is infinitely easier to design better things with all the accumulated experience and know-how of what works and what doesn't under your belt. It would have been truly horrifying if that had not been the case.
That being said, Rust is really about lifetimes. That's the big ticket selling point. My point above was that 1) it isn't a silver bullet and 2) it can be a real hindrance is many applications.
flohofwoe · 1d ago
Tbh, only a mother can love Rust's syntax. It has even more "punctuation" than C++.
dlahoda · 1d ago
what is this 1 category?
flohofwoe · 1d ago
When looking at the number of CVEs you'll also have to take the amount of code written in that language into account, a language which has no users also has no CVEs ;)
I bet there's easily tens of thousands of times more C++ code than Rust code out there.
Igwe_404 · 1d ago
I need more background and foundational insights for C++ control flow syntax for a pythonista who knows c like: I++ do {} while switch () case.
1. all the .h files were compiled, and emitted as a binary that could be rolled in all at once
2. each .h file created its own precompiled header. Sounds like modules, right?
Anyhow, I learned a lot, mostly that without semantic improvements to C++, while it made compilation much faster, it was too sensitive to breakage.
This experience was rolled into the design of D modules, which work like a champ. They were everything I wanted modules to be. In particular,
The semantic meaning of the module is completely independent of wherever it is imported from.
Anyhow, C++ is welcome to adopt the D design of modules. C++ would get modules that have 25 years of use, and are very satisfying.
Yes, I do understand that the C preprocessor macros are a problem. My recommendation is, find language solutions to replace the preprocessor. C++ is most of the way there, just finish the job and relegate the preprocessor to the dustbin.
Yup, I think this is the core of the problem with C++. The standards committee has drawn a bad line that makes encoding the modules basically impossible. Other languages with good module systems and fast incremental builds don't allow for preprocessor style craziness without some pretty strict boundaries. Even languages that have gotten it somewhat wrong (such as rust with it's proc macros) have bound where and how that sort of metaprogramming can take place.
Even if the preprocessor isn't dustbined, it should be excluded from the module system. Metaprogramming should be a feature of the language with clear interfaces and interactions. For example, in Java the annotation processor is ultimately what triggers code generation capabilities. No annotation, no metaprogramming. It's not perfect, but it's a lot better than the C/C++'s free for all macro system.
Or the other option is the go route. Don't make the compiler generate code, instead have the build system be responsible for code generation (calling code generators). That would be miles better as it'd allow devs to opt in to that slowdown when they need it.
Yes, this was tedious, but we do it for each of our supported platforms.
But we can't do it for various C libraries. This created a problem for us, as it is indeed tedious for users. We created a repository where people shared their conversions, but it was still inadequate.
The solution was to build a C compiler into the D compiler. Now, you can simply "import" a C .h file. It works surprisingly well. Sure, some things don't work, as C programmers cannot resist put some really crazy stuff in the .h files. The solution to that problem turned out be we discovered that the D compiler was able to create D modules from C code. Then, the user could tweak by hand the nutburger bits.
This is the same solution that Apple chose for Swift <-> Objective C interop. I wonder if someone at Apple was inspired by this decision in D!
Herb Sutter, Andrei Alexandrescu and myself once submitted an official proposal for "static if" for C++, based on the huge success it has had in D. We received a vehement rejection. It demotivated me from submitting further proposals. ("static if" replaces the C preprocessor #if/#ifdef/#ifndef constructions.)
C++ has gone on to adopt many features of D, but usually with modifications that make them less useful.
- D had this feature long before C++ did.
- It isn't the same thing as "static if". Without "static if", conditionally compiling variables into classes is much more elaborate, basically requiring subclassing to do, which is not really semantically how subclassing should be used (the result is also way more confusing and oblique than the equivalent directly expressed construct you'd have using "static if").
It seems particularly tricky to define a template in a module and then instantiate it or specialize it somewhere else.
D also has an `alias` feature, where you can do things like:
where from then on, `abc.T` can be referred to simply as `Q`. This also eliminates a large chunk of purpose behind the preprocessor.C++ has adapted the ‘using’ keyword now to seem fairly similar to alias, but can’t completely subsume macros unfortunately.
It replaces the preprocesser:
with hygiene. Once you get used to it, it has all kinds of nice uses.For normal functions or classes, we have forward declarations. Something similar needs to exist for templates.
D does not require names in global scope to be declared lexically before they are used. C++ only does this for class/struct scopes. For example:
compiles and runs (and runs, and runs, and runs!!!).But how do you handle a template substitution failure? In C++:
The compiler has no idea whether bar(1, 2); will compile unless it parses the full definition. I don't understand how the compiler can avoid parsing the full definition.The expensive bit in my experience isn't parsing the declaration, it's parsing the definition. Typically redundantly over thousands of source files for identical types.
This seems incredibly wasteful, but of course still marginal better than just #including code which is the alternative.
The next major advance to be completely ignored by standards committees will be the 100% memory safe C/C++ compiler, which is also implemented and works amazingly well: https://github.com/pizlonator/fil-c
Tools like ccache have been around for over two decades, and all you need to do to onboard them is to install the executable and set an environment flag.
What value do you think something like zapcc brings that tools like ccache haven't been providing already?
https://en.wikipedia.org/wiki/Ccache
It avoid instantiating the same templates over and over in every translation unit, instead caching the first instantiation of each. ccache doesn't do this: it only caches complete object files, but does not avoid repeated instantiation costs in each object file.
I'm afraid this feature is at best a very minor improvement that hardly justifies migrating a whole compiler. To be blunt, it's not even addressing a problem that exists or makes sense to even think about. I will explain to you why.
I've been using ccache for years and I never had any problem getting ccache to support template code. Why? Because the concept of templates is ortogonal to compiler caches. It matters nothing, if you understand how compiler caches work. Think about it. You have the source file you are compiling, you have the set of build flags passed to the compiler, and you have the resulting binary.
That's the whole input, and output.
It's irrelevant if the code features templates or not.
Have you checked if the likes of zapcc is fixing a problem that actually doesn't exist?
Here are some performance numbers: https://www.phoronix.com/news/Zapcc-Quick-Benchmarks
> To be blunt, it's not even addressing a problem that exists or makes sense to even think about. I will explain to you why.
Do you talk down to people like this IRL as well?
> I've been using ccache for years and I never had any problem getting ccache to support template code.
What I said is that zapcc has a different approach that offers even more performance benefits, answering the question of what zapcc offers that ccache doesn't offer.
> if you understand how compiler caches work. Think about it.
There's no need to use "condescending asshole" as your primary mode of communication, especially when you are wrong, such as in this case.
If you look at the benchmarks you just quoted, you see cache-based compilations outperforming zapcc in quite a few tests. I wonder why you missed that.
The ones that ccache fares as well as builds that don't employ caching at all are telling. Either ccache was somehow not used, or there was a critical configuration issue that prevented ccache from caching anything. This typically happens when projects employ other optimization strategies that mess with ccache, such as pipelining builds being enabled or extensive use of precompiled headers.
The good news is that in both cases these issues are fixed by either by actually configuring ccache or disabling these other conflicting optimization strategies. To be able to tell, it would be necessary to troubleshooting the build and take a look at ccache logs.
> Do you talk down to people like this IRL as well?
Your need to resort to personal attacks is not cool. What do you hope to achieve, other than not sounding like an adult?
And do you believe that pointing out critical design flaws is "talking down to people"?
My point is very clear: your baseline compiler cache system, something that exists for two decades, already supports caching template code. How? Because it was never an issue to begin with. I explained why: because a compiler cache fundamentally caches the resulting binary given a cache key, which is comprised of data such as the source file provided as input (basically the state of the translation unit) and the set of compiler flags used to compile it. What features in the translation unit is immaterial. It doesn't matter.
Do you understand why caching template code is a problem that effectively never existed?
> What I said is that zapcc has a different approach that offers even more performance benefits, answering the question of what zapcc offers that ccache doesn't offer.
It's perfectly fine if you personally have a desire to explore whatever idea springs to mind. There is no harm in that.
If you are presenting said pet project as any kind of solution, the very least that is required of you is to review the problem space, and also perform a honest review of the solution space. You might very well discover that your problem effectively does not exist, because some of your key assumptions do not hold.
I repeat: with pretty basic compiler caches, such as ccache which exists for over two decades, the only thing you need to do to be able to cache template code is to install ccache and set a flag in your build system. Tools such as cmake already support it out of the box, so onboarding work is negligible. Benchmarks already show builds with ccache outperforming builds with the likes of zapcc. What does this tell you?
Someone else in this thread already pasted benchmarks. The observation was, and I quote:
> Zapcc focuses on super fast compile times albeit the speed of the generated code tends to be comparable with Clang itself, at least based upon last figures.
You used many words just so say "ccache is a build cache".
> it still needs to rebuild the whole translation unit from scratch if a single line in some header changes.
You are using many words to say "ccache rebuilds a translation unit when it changes".
What point were you trying to make?
Frankly your attitude in this whole thread has been very condescending. Being condescending and also not understanding what you're talking about is a really bad combination. Reconsider whether your commenting style is consistent with the HN guidelines, please.
> It's not even possible to link to unsafe code.
This makes it rather theoretical.
It wraps all of the typical API surface used by Linux code.
I’m told it has found real bugs in well-known packages as well, as it will trap on unsafe but otherwise benign accesses (like reading past one the end of a stack buffer).
EDIT: Oh, found the tradeoff:
hollerith on Feb 21, 2024 | prev | next [–]
>Fil-C is currently about 200x slower than legacy C according to my tests
But also consider that it's one guy's side project! If it was standardized and widely adopted I'm certain the performance penalty could be reduced with more effort on the implementation. And I'm also sure that for new C/C++ code that's aware of the performance characteristics of Fil-C that we could come up with ways to mitigate performance issues.
For the high-end performance-engineered cases that C++ is famously used for, the performance loss may be understated since it actively interferes with standard performance-engineer techniques.
It may have a role in boring utilities and such but those are legacy roles for C++. That might be a good use case! Most new C++ code is used in applications where something like Fil-C would be an unacceptable tradeoff.
Of course it was completely ignored. Did you expect the standards committee to enforce caching in compilers? That's just not its job.
> The next major advance to be completely ignored by standards committees will be the 100% memory safe C/C++ compiler, which is also implemented and works amazingly well: https://github.com/pizlonator/fil-c
Again—do you expect the standards committee to enforce usage of this compiler or what? The standards commitee doesn't "standardize" compilers...
I think there is a hefty deal of ignorance in your comment. A standardization process is not pull-based, it's push-based.
If you feel you have a nice idea that has technical legs to stand, you write your idea down and put together a proposal and then get in touch with committee members to present it.
The process is pretty open.
> Certainly more useful than anything else the standards committees have done in the past 10 years.
Do you understand the "standards committee" is comprised of people like you and me, except they got off their rear-end and actually contribute to it? You make it sound like they are a robe-wearing secret society that is secluded from the world.
Seriously, spend a few minutes getting acquainted with the process, what it takes to become a member, and what you need to do to propose something.
Of course, these tools are of interest to the broader C++ community. Thanks for sharing.
>Both zapcc and Fil-C could benefit from the involvement of the standards committee.
What exactly does the standards committee do for these software projects without being involved in their development? I think there is nothing to do here that is within the scope of the language itself. Of course, if the creators of those projects come up with a cool new idea, they can submit to the standards committee for comment. They can also comment on new standards that make the tools not work anymore. But that is help going from the project to the committee, not the other way around.
That's great to hear. It sounds like you have everything set to put together a proposal. Do you have any timeline in mind to present something?
> I'm saying that the committees should acknowledge their existence, (...)
Oh does this mean any of the tools you're praising was already proposed to be included in the standard? Do you mind linking to the draft proposal? It's a mailing list, and all it takes is a single email, so it should be easy to link.
Where's the link?
https://isocpp.org/std/submit-a-proposal
There are also quite a few compiler cache systems around.
For example, anyone can onboard tools like ccache by installing it and setting an environment variable.
I'm sure you must be aware, these compiler tools do not constitute a language innovation. I'd also imagine that both are not productions ready in any sense, and would be very difficult to debug if they were not working correctly.
C++ build times are actually dominated by redundant parsing of headers included in multiple .cpp files. And also redundant template instantiations in different files. This redundancy still exists when using ccache.
By caching the individual language constructs, you eliminate the redundancy entirely.
ccache doesn't add anything over make for a single project build.
Would have been dirt simple to migrate existing codebases over to using it (find and replace include with import, mostly), and initial implementations of it on the compiler side could have been nearly identical to what's already there, while offering some easy space for optimizing it significantly.
Instead they wanted to make an entirely new thing that's impossible to retrofit into existing projects so its basically DOA
The committee has taken backwards compatibility, backwards - refusing to introduce any nuance change in favor of a completely new modus operandi. Which never jives with existing ways of doing things because no one wants to fix that 20 year old codebase.
We want new stuff, but in order to do that we must break old stuff. Like breaking old habits, except this one will never die.
c++26 still builds and runs that c++98 and so we can use it. Rust is nice but it can't interoperate as well in many ways and so it gets little use. you can call that bad desingn - I might even agree - but we are stuck.
I empathize. This is where rust could really help but there’s a lot of hate around “new” so it sits. The C++2042 standard will still have to be able to solve for C++98. The language will die. A pointer is a pointer and it shouldn’t need weak_ptr, shared_ptr, unique_ptr etc. If you expose it, it’s shared. If you don’t, then it could be unique but let the compiler decide. The issue with all these additions is they are opt in for a community that would rather opt out since it means learning/rewriting/refactoring.
I’ve come across this so many times in my career. Thank goodness for AI and LLMs that can quickly decompose these hairball code bases (just don’t ask it to add to it).
I love C/C++ but it’s so old at this point that no sane person should ever start with that.
I could care less about shared_ptr.
The issue is why should I care? Why is it on the dev to determine how a pointer should work? Why the dev has to go back and refactor old code to be new again? Why can’t the committee build non-breaking changes to the spec? I would rather have compile flags that make a pointer an “old pointer style” vs having to mentally juggle which ptr container to use, when, and why.
This is just one example. Gang of 3, becomes gang of 5, becomes mob of state… It’s just a giant mess.
As in `#include <vector>` also performs `#include <iterator>` but `import vector` would only import vector, requiring you to `import iterator`, if you wanted to assign `vec.begin()` to a variable?
Or is it more like it shouldn't matter in which order you do an import and that preprocessor directives in an importing file shouldn't affect the imported file?
you define #import as "include but no context leaks into it" and that should on its own be enough to let the compiler just, compile that file once and reuse it wherever else its imported. That's like 95% of the benefit of what modules offered but much much simpler
Explicit sub-unit encapsulation. True isolation. No more weird forward declarations, endlessly nested ifdef guards, or insane header dependency graphs. Things just exist as they are separate, atomic, deterministic and reliable.
Modules probably need a revision, and yes, adoption has been slow, but once you start using modules you will never go back. The clarity of explicitly declared interfaces and the freedom from header hell fundamentally changes how you think about organizing C++ code.
Start a new project with modules if you don’t believe me. Try them. That is the largest barrier right now - downstream use. They are not supported because they are not used and they are not used because they are not well supported. But once you use them you will start to eagerly await every new compiler release in hopes of them receiving the attention they deserve.
It's unlikely at this point modules in their current form will ever be anything but a legacy feature in C++. Maybe someday a new implementation will arise, just like noexcept replaced the failed "throws" declaration.
> No more weird forward declarations
We c++ devs just have collectively accepted that this hack is still totally ok in the year 2025, just to improve build times.
I made it one of my first projects after joining the Chrome team to fix that gap, which we documented at [1]. (This reminds me of the article's "The only real way to get those done is to have a product owner...".)
You could even stretch the analogy to talk about how standard JS modules compete against the hacked-together solutions of AMD or CommonJS modules, similar to C++ modules competing against precompiled headers.
That said, the C++ modules problem seems worse than the JavaScript one. The technical design seems harder; JS's host/language separation seems cleaner than C++'s spec/compiler split. Perhaps most importantly, organizationally there was a clear place (the WHATWG) where all the browsers were willing to get together to work on a standard for JS module loading. Whereas it doesn't seem like there's as much of a framework for collaboration between C++ compiler writers.
[1]: https://blog.whatwg.org/js-modules
Modules are horrible for build times. If you change an implementation (i.e something that would not normally involve editing a header) the amount of rebuilding that happens is crazy, compared to any C++ project that was set up with a minimal amount of care.
In terms of rebuild performance in defining the interface vs implementation, there is interest in having rustc handle that automatically, see https://rust-lang.github.io/rust-project-goals/2025h2/relink...
I think you're going to want to define "made it big" and then make yourself some lists as this to me sounds like it doesn't have much explanatory power.
See Zig for a working example of that idea (which also turned out to be a viable cmake replacement for C and C++ projects)
It could even be reduced to compiler vendors agreeing on a convention to compile and run a build.cpp file. Everything else could live in 3rd party code shipped with a project.
good build systems need to bea real programming language. The more declaritive the better - this is different from the project that often should be a different style
Instead, I launched a compilation.
Yes. The C++ standard is an abomination that is both over-specified and wildly under-specified.
Modules are simply never going to work en masse. It is a failed idea.
Maybe I don't know what they mean by this, but the header inclusion way is O(N*M), is it not? Where N is the number of source files and M is the average number of dependencies each has (as expressed as #includes).
If M is approaching N -- meaning everything depends on everything -- you probably have bigger problems than compile times.
That's also setting aside precompiled headers. Those wouldn't be N^2 either, even when the precompiled header needs to be precompiled.
The pre processor and linker, as derided as they are, allow for scaling of software size to extremes not possible with supposedly superior languages’ supposedly superior module systems.
Want to build a >billion line of code OS? You can do that with headers and separate compilation units. Good luck doing that with any other tech
Someone somewhere is trying to vibe code the entirety of the Linux kernel using Copilot.
It's a fact that:
- Super large software tends to be C or C++
- One of the unique features of C and C++ is the way software is organized (headers and separate compilation).
- Attempts to bring other languages' approach to modules to C++ have been a complete disaster.
Hence it's an empirical fact that C and C++ have a superior approach to software scaling, and that approach is headers and separate comp. And a preprocessor with macros.
I'm not convinced the "superior" follows. What I'm missing is a causal link between your first and second points - super large software could have been written in C or C++ for reasons other than the header/implementation split (especially if the super large software wasn't so super large at first and grew into its size instead of being planned for that size from the start), and somewhat conversely programming languages with header/implementation splits don't necessarily have corresponding super large software (e.g., OCaml, at least publicly?).
I didn't think modules and separate compilation units are (completely) mutually exclusive? It's not like modules force you to use a single compilation unit for all your code; it just changes where the compilation unit boundaries/dependencies are (though to be fair I'm not entirely certain how much heavy lifting that "just" is doing)
> Want to build a >billion line of code OS? You can do that with headers and separate compilation units. Good luck doing that with any other tech
It's not immediately obvious to me why this must be the case. That's the fundamental limitation of modules systems that supposedly prevents this scaling?
Not the person you're replying to but I can see a problem with some dependency chains.
Let's say you have: stdlib <- A.hpp <- B.hpp (small header) <- C.hpp (likewise) <- many .cpp files.
If you only precompile A.hpp (as is commonly done), the many .cpp files can be compiled in parallel once A.hpp is precompiled, and you get a nice speedup.
If on the other hand you need to precompile everything, then all these cpp files must wait on A, then B, then C to be precompiled
Speaking more abstractly even if there isn't an explicit interface/implementation separation perhaps compilers could pick out and make available interface information "ahead of time" to alleviate/possibly eliminate the effect of otherwise problematic dependency chains? For example, in Rust you "just" need to look for signatures to figure out the interface and you have the corresponding keywords to make searching for those easy without having to fully parse the entire file.
There's also the question of whether super large projects must have problematic dependency chains - hypothetically, if a super large project were written in a language with a module system some work would be done to try to structure it to minimize build time/system pain points. I don't think I can confidently say that that is always (im)possible.
I'm not sure how well this would work for non-instantiated templates
> There's also the question of whether super large projects must have problematic dependency chains
Any header precompilation dependency chain is a dependency chain and may end up worse than fully parallel TU compilation if the time to parse said headers is faster than the time to compile them in a serial way.
I can see modules being used, but relegated to, "import std; import fmt; import vulkan (etc)", typically use cases one should already use PCH for.
See the book "Large Scale C++ Software Design" by John Lakos for more detail in this direction.
C++ has no future
Not great!
C++ code bases are really a lot longer-lived than any other software builds upon them. Hence we cannot drop it.
Starting with C++17, I think committee has been doing the language a disservice and piled even more and more unintended complexity by rushing "improvements" like modules.
I don't write C++ anymore due to my team switching to Rust and C only (for really old stuff and ABI). I am really scared of having to return though. Not because I am spoiled by Rust (I am though), but because catching up with all the silly things they added on top and how they interact with earlier stuff like iterators. C++ is a perfect language for unnecessary pitfalls. Newer standards just exploded this complexity.
https://en.cppreference.com/w/cpp/locale/codecvt_utf8.html
https://en.cppreference.com/w/cpp/algorithm/random_shuffle.h...
Mozilla and Dropbox did it. LLMs are good at translating between languages, and writing unit tests to make sure things still work the same.
12%. Assume the progress is linear (not logarithmic like most cases), we just need 60 more years to migrate those c/c++ code.
Rust just doesn't have close to the same type of adoption/support yet, especially when considering various embedded platforms.
And for the safe parts, the posts that I've read from people who have spent a non-trivial amount of effort with the language do not paint a clear picture either on whether there was really a benefit to the language overall.
So to say that "the world has moved on" in light of all of this is pure hubris.
C++ as C with classes is a pretty good language!
No comments yet
- GCC switched from C to C++
- CUDA switched from C to C++
But I can understand the decision, and at that time , C++ frontend features and libs were a little bit less horrible.
Please explain in detail how alternatives would have worked better for GCC and CUDA. Also, if you could share some historical context about how those alternatives could realistically have been implemented at the time, that would be helpful too.
I love to hear all the "would've" and "should've" scenarios.
The C++ frontend of MSVC handles (the common) compiler-specific language extensions differently than the other compilers. Besides, its pre-processor behaves differently too. It is now good that there is a clang frontend for MSVC.
If you think that "the world seems to have moved on to Rust", I would recommend to look at the job listings. For example, devjobs.de: Rust: 61, C++: 1546. That's 1:25. Maybe in other countries there are more Rust jobs?
https://play.rust-lang.org/?version=nightly&mode=debug&editi...
These are all highly non-parallel problems. They don't gain much from being parallel, and because Rust imposes 'restrict' semantics on even single threaded code you end up making it much harder to write code in these domains.
This has been my experience with Rust. Shared mutability is safe on a single thread without 'restrict' on all your pointers, and Rust has limited options to opt into shared mutability (with lots of ergonomic caveats).
Don't get me wrong though, I still think Rust is a great tool. It just has tradeoffs.
The idiomatic Rust equivalent of a C non-restrict pointer is arguably &Cell<T>. The biggest problem with it is library code that takes &mut T when the likes of &Cell<T> might suffice (because potential aliasing does not affect the semantics of what that Rust code is doing), but this is an acknowledged problem and the Rust project will take pull req's that fix it where it occurs.
If you're sure you're never going to need multi-threaded environment, you have an option as well: Replace std::sync with std::rc, Mutex with RefCell in the above toy example and that's about it.
If you want to use some asynchronous runtime, replace std::sync with tokio::sync (or std::rc), slap async/awaits along with a single-threaded runtime and that's about it.
Of course, the code above is just a toy example and business logic is much more complex in real world, but compare this to what it would take to write same C++ logic in async.
I found Rust's approach massively more ergonomic compared to C++ approach of passing closures around for asio-like IO contexts or coroutine compiler-magic which opens new novel avenues to shoot myself on the foot, well, to the extent I could grasp it.
It's true Rust forces you to pay all this cost ahead of time. It's also true most applications don't require this level of safety really, so it becomes ridiculous to pay it upfront. And even for some that require such high level of safety, you can skip bunch of bolts on a plane door and it will still be a billion dollar company at the end of the day, so...
You’re forced into function-call or jump-table dispatch, which tends to be slower.
On the other hand, there's the recently-added-to-nightly `become` for guaranteed tail calls, which might work better than computed gotos if CPython is a good example [0]
> When using both attributes [[[clang::musttail]] and preserve_none], Jin's new tail-call-based interpreter inherits the nice performance benefits of the computed-goto-based version, while also making it easier for the compiler to figure out optimal register allocations and other local optimizations.
To be fair I don't think Rust has a preserve_none equivalent yet, but given naked functions are a thing in Rust I'd hope it isn't too bad to support?
[0]: https://lwn.net/Articles/1010905/
The C++ solution would be to start the threads and use an MPSC queue (which, ironically, Rust also has) in order to update the UI.
Rust will eventually stumble upon ergonomics and allow portions of code to be specified as single threaded or embarrassingly parallel, but unfortunately the community evolved horse blinders early on and isn't letting them go any time soon.
And give Rust one or two more decades and it will be the same messy kitchen sink language as C++. If anything, Rust is speedrunning C++ history (with the notable exception of fixing static memory safety of course).
Internet hype meets actual industry reality :-).
I'm thinking the same about your comment :D
There are things you can add, but the rot still permeates the foundations, and much of the newness goes partially unused because they're just not at home in C++. Use of `std::optional` and `std::variant` is, as far as I know, still limited, even in newer C++ code, because the ergonomics just aren't there.
variant isn't, yet. We'll eventually get some kind of structural pattern matching that will make variant or it's successor more idiomatic.
C++ does have quite a bit of rot, you're right. But that's the price of building technology people actually use.
Carbon doesn't seem to have any fundamentally new ideas, we'll see how well it fares in the wild.
Thats the fantastic thing about c++, you can already write an easy to use match, but they just chose not to include that in the stdlib but rather want you write that yourself.
Example:
Also optional and expected are ergonomic nightmares since there is no "try!" macro like rust has. In general it lacks the infrastructure that is needed to make these types nice to use. It also clashes with other concepts like RAII where you kinda have to use exceptions when it comes to ctors that may fail.For example, if you had to match patterns to distinguish between these three possibilities when looking at a tree: (val), (val (tree left) (tree right)), (val (tree subtree)). Here, the possibilities are not really separate types, but rather different ways of constructing a type. This sort of pattern shows up in general purpose programming (even meat and potato imperative programming) pretty often.
There was a proposal for this championed by Stroustrup over ten years ago, but it went nowhere IIRC. https://www.stroustrup.com/pattern-matching-November-2014.pd...
I have some hope that the upcoming compile time reflection will make it easier to implement said syntactic sugar.
So I'm afraid no by definition C++ can't adopt Rust's ideas because Rust's ideas were originally impossible C++ ideas.
I agree with your point except for the 'never' qualifier. It was certainly true when Rust was born.
C++ has proven the 'never' part wrong multiple times. I think, by 2030, the only thing that C++ would lack that Rust has right now is the unified toolchain/packaging ecosystem because people are not going to settle that debate.
Everything else is well on its way to be implemented, in one of three forms - core language features (eg: concepts), language features that primarily enable writing more powerful libraries so that you do not have to come up with language features for everything (eg: reflection), and finally tooling support from the compiler as a test bed of what the language could guarantee (lifetime checks and annotations in clang).
Of course Rust is innovating pretty well too, I am very interested in seeing what async/coroutines are going to look like in a few years.
You are free to design your library in a way that your users only see one of these.
C++ didn't get that. The proposal paper at the time says it's impossible (for C++). But what they did propose was the feature you've seen in C++ today, which they call "move", but it has slightly odd (though convenient to implement, Worse Is Better after all) behaviour.
Now you can make this C++ "move" behaviour out of destructive move, that behaviour is roughly what Rust calls std::mem::take and it's sometimes useful, which is why that function is provided. But, often it's not really what you wanted, and if you actually wanted destructive move but only have this C++ imposter then you need to perform the entire take, then throw away the newly created object. You will find lots of C++ code doing exactly that.
So, no, you can't "design your library" to deliver the desirable property in C++. It's just another of the dozens of nagging pains because of design mistakes C++ won't fix.
Yes, I understand that there’s a lot of bad code out there and C++ happily enables that. But that was not my point.
Entire API’s are designed around this: my type that tracks a background task can have a shutdown() method that moves self, so that if you call foo.shutdown(), you can’t use foo any more.
This is more than just preventing a value from being used, it also facilitates making a rule that “moves are only just memcpy()”, and it can actually be enforced:
C++ move semantics require you to write arbitrary code that pillages the moved-from value (like setting the heap pointer of a moved-from value to nullptr) to ensure the old value is safe: rust just says “nope, there’s no constructor, moves are just a memcpy. We will keep it safe by simply not letting code use the old value.”
C++ can never have this unless they offered yet another mutually incompatible form of move semantics (lol, what would the sigil be? &*&?)
template <typename T> void drop(std::unique_ptr<T> &&) {}
Also, you don’t really need this because of RAII. You can make it simpler, and here’s how you’d do this in C++26.
template <std::movable T> void drop(T &&) {}
It can get even simpler!
void drop(std::movable auto){}
Do you see my point about C++ incorporating the good ideas at a glacial pace?
So funny thing, your C++ 26 solution doesn't do what my Rust function does.
Actually even the first one doesn't, but you likely don't really care about a std::unique_ptr, so it doesn't feel like a difference, and the negligence only really bites when it's not "just" that pointer type.
You see that Rust function destroyed the T. It's gone, no more T. But your C++ function makes a new T, then destroys the old one, so there's still a T, it's not gone after all.
Perhaps your idea of C++ semantics is a bit off?
Is that the joke here? That despite everything you didn't understand why core::mem::drop has that definition and so reading the empty body you assumed that you can just not do anything and that'll work in C++ ?
https://godbolt.org/z/58TqTTM37
https://godbolt.org/z/zM3oxjrfn
and contrast this Rust:
https://rust.godbolt.org/z/1rMYcqY65
In your unique_ptr<T> example what you'd hidden (from me? Or perhaps from yourself) was that we're not destroying the unique_ptr, we're just destroying the Foo, and since the unique_ptr is null the destructor for that will be silent when the scope ends.
So what happens for objects that aren't behind a unique_ptr?
> Also, you don’t really need this because of RAII.
drop() indeed isn't used much because of RAII, but it is handy for those instances where you do actually want to dispose of something "early".
> dispose of something "early".
This is a bit of an anti pattern in C++, but doable of course.
This is useful in the grpc library I use, which has a response type with some metadata about the response, and an “inner” value that represents the thing the grpc method returned. If you just want the inner thing and don’t care about the metadata, there’s a `fn into_inner(self)` that destroys the response and leaves you the inner value.
You can write similar methods in C++ but they require leaving the original value in an “empty” state so that you can avoid expensive copying of things while still letting the moved-from value be “safe” to use. But that affects your API design… you now have to make the methods that fetch the “inner” thing be nullable/optional so that you can represent “oh this was moved from so there’s no body any more”. You don’t have to do that in Rust: moves are just a memcpy and the compiler will reject programs that use the moved-from value.
Yes, this is something the C++ 'language' is not going to specify other than claiming that it is undefined behavior.
Doesn't prevent compilers from doing it though, clang will happily do this for you right now in most cases.
https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-a...
The RFC you posted has nothing to do with move semantics, it's about references outliving what they point to (ie. use-after-free, etc) and similar to Rust's borrow checker.
But here's the thing: move semantics and the borrow checker have nothing to do with each other! The borrow checker ensures that borrowed data (ie. &Foo, equivalent to C++'s references) is sound, it's not the part that enforces move semantics. That happens earlier in the compilation, the compiler enforces moves well before the borrow checker phase.
I don't think those work either. Not only do neither of those actually end the lifetime of what's passed in, but they have other flaws as well.
> template <std::movable T> void drop(T &&) {}
This literally does nothing. Reference parameters don't affect what's passed in on their own - you need at least something on the other end (e.g., a move constructor) to do anything.
For example, consider how this would be instantiated for std::vector<int>:
> void drop(std::movable auto){}I believe this is equivalent to passing by value, so this will actually invoke a copy if what's passed in isn't eligible for a move (or if the move constructor is equivalent to the copy constructor, since copyable subsumes movable). Again, consider how this would be instantiated for std::vector<int>:
> > dispose of something "early".> This is a bit of an anti pattern in C++, but doable of course.
I don't think you can do quite the same thing in C++ without introducing new scopes.
>For example, consider how this would be instantiated for std::vector<int>:
Great, here you go.
https://godbolt.org/z/9v66n6Ta4
And yes, the compiler will not prevent you from using this value. (clang will eventually, I think)
But clang static analyzer will happily detect it.
https://stackoverflow.com/questions/72532377/g-detect-use-af...
We're talking about how the rust compiler uses move semantics to prevent you from using the moved-from value, such that code like this will not compile:
C++'s move semantics do not prevent you from using f after you've moved it. On the contrary, it's intentionally allowed. It's not undefined behavior either, it's "unspecified" behavior, which means "behavior, for a well-formed program construct and correct data, that depends on the implementation". This simply means that it's up to the individual type to decide what happens when a value is moved from. (A string becomes an empty string, for instance, or a vector becomes and empty vector.)Rust's move semantics mean:
- You don't write a move constructor
- Moves are just memcpy
- The compiler enforces the old value can't be used any more
C++'s move semantics mean:
- You must write a move constructor (rvalue reference constructor)
- Moves are arbitrary code
- The language explicitly allows the moved-from value to continue to be used
That there are certain linter-style tools like clang-tidy which can be configured to warn on using moved-from values is irrelevant: The standard explicitly allows it. It's personal preference whether you should make a habit of using moved-from values in your codebase, which is why this will only ever be a linter thing. The C++ standard would have to completely change its mind and retcon moves to mean something different, if they ever wanted to change this.
Now, the beginning of this thread was someone saying "Rust is basically the wanted fixes to C++ that C++ itself could never adopt for legacy reasons". Then you came back with "I agree with your point except for the 'never' qualifier", implying C++ will eventually support Rust's ideas. But move semantics in C++ are precisely the opposite of those in Rust, because rust-style semantics were deemed impossible to implement in C++, even though it's what people actually wanted at the time. So I think it's fair to say C++ will "never" get Rust-style move semantics.
I agree.
Probably depends on what you mean by "works out". I don't think GP would agree that delivering a less capable alternative qualifies.
For example, one major feature C++0x concepts was supposed to have but got removed was definition-time checking - i.e., checking that your template only used capabilities promised by the concepts it uses, so if you defined a template with concepts you could be assured that if the definition compiled it'd work with all types that satisfied the concept. That feature did not make it to C++20 concepts and as far as I know there are no plans on the horizon to add that feature.
C++26 concepts has more or less everything you mention, and you can try it out with all the major compilers right now.
C++ 0x is what people called the proposed new C++ language standard from about 2005 through 2009 or so under the belief that maybe it would ship in 2008 or 2009. Because you're here, now, you know this didn't end up happening and actually the next standard would be C++ 11. For a little while they even jokingly talked about C++ 0A where A is of course hexadecimal for ten, but by the time it was clear it wouldn't even make 2010 that wasn't funny.
So C++ 0x isn't five years ago, it's about 15-20 years ago and in this context it's about the draft revision of C++ in which for some time the Concepts feature existed, but Bjarne insisted that this feature (which remember is roughly Rust's traits) was not implementable in reasonable time, and frankly was not as much needed as people had believed.
This argument swayed enough committee members that Concepts was ripped back out of the draft document, and so C++ 11 does not have Concepts of any sort. Because this particular history is from the relatively recent past you can go read the proposal documents, there might even be Youtube videos about it.
OK, so, now you at least know what these terms mean when other people use them, that can't hurt.
As to your next claim er, no, not even close. Barry Revzin wrote a really nice paper connecting the dots on this, which probably passed into legend specifically for saying hey C++ 0x Concepts are the same thing as Rust traits. C++ proposal paper P2279 is what you're looking for if that interests you. That'll be less confusing for you now because you know what "C++ 0x" even means.
Now, Barry wrote that paper in the C++ 23 cycle, and we're now at / just past the end of the C++ 26 cycle, but I assure you that nothing relevant has changed. You can't magically have model checking in C++ that's not there. You can't provide concept maps, it's not in the language and so on.
But as it stands, I expect that whatever innovations these languages produce will be picked up by C++ in ~5 years.
This is exactly WHY we dont see a rush movement of C++ developers to Rust throwing away everything for Rust. Rust is trying to solve problems that already not exist 99.9999% of time in modern C++ code style and standards.
Also, some day C++ compilers or tooling will get its own Borrow Checker to completely forget about Rust - this will be done just for fun just to stop arguing with rust-fans :)
The number of people I met in Rust conferences that rewriting at least parts of rather big C++ codebases weren't small either.
However, there is still big amount of code that is purely C++. Many of the older code bases still use C++03-style code too. Or they were written in the OOP design pattern golden era that requires huge reactors to adapt functional / modern code. Anything with Qt will not benefit from smart pointers. Even with Qt 6.
Rust cannot solve these problems since the challenges are not purely technical but social too.
No amount of fallible human vigilance will stop you from forgetting the existence of a C++ quirk in the code you're rushing out before heading out for the night. Human oversight does not scale.
Rust solves 1 category of problems in a way that is not without its costs and other consequences. That is it. There are projects where this is very important, there are other projects where its virtually useless and the consequences just get in the way. It is not magic. It doesn't make anything actually 'safe'.
That being said, Rust is really about lifetimes. That's the big ticket selling point. My point above was that 1) it isn't a silver bullet and 2) it can be a real hindrance is many applications.
I bet there's easily tens of thousands of times more C++ code than Rust code out there.