Google has been using Rust in Android more and more for its memory safety characteristics, and the results on that front were quite positive. It turns out, however, that not only does using Rust reduce the number memory safety issues, it’s also apparently a lot faster to code in Rust than C or C++.
We adopted Rust for its security and are seeing a 1000x reduction in memory safety vulnerability density compared to Android’s C and C++ code. But the biggest surprise was Rust’s impact on software delivery. With Rust changes having a 4x lower rollback rate and spending 25% less time in code review, the safer path is now also the faster one.
↫ Jeff Vander Stoep at the Google Security Blog
When you think about it, it actually makes sense. If you have fewer errors of a certain type, you’ll spend less time fixing those issues, time which you can then spend developing new code. Of course, it’s not that simple and there’s a ton more factors to consider, but on a base level, it definitely makes sense. Spellcheck in word processors means you have to spend less time detecting and fixing spelling errors, so you have more time to spend on actually writing.
I’m sure we’ll all be very civil about this, and nobody will be weird about Rust at all.

While I do think that some time is saved on fixing memory-related bugs, I do not think that is the primary driver of the increased productivity.
Rust is a modern language that feels familiar coming from other modern languages like Kotlin or Swift. It is much more expressive, allowing you to say more in fewer lines. I’m sure an argument can be made for the latest C++ on the basis of it bolting on some of these features onto an ever-increasing pile of features, but I would still take almost any language over C++ any day (and I used to write C++ for Microsoft).
C is respectable, of course, but it is very low level compared to Rust, so obviously it takes more time to write it.
Programs written in Rust have equivalent (or even better, due to the lack of harmful Undefined Behavior baked into the language) performance to C/C++. There’s no magic in compilers. If someone thinks that if they write code like “x++” or “++x” and the compiler will translate it directly to “inc eax” (and that it would make a difference in the final assembly code), then their knowledge about compilers is based on last century practices.
Yes, that used to be the case in the ’90s, but now it’s far from reality. Modern compilers operate on SSA (Static Single Assignment), and C/C++/Rust/whatever code is translated into the same Intermediate Representation for any of these languages. In modern compilers, the “x++” can even be eliminated completely.
Then multiple transformation and optimization passes are performed on multiple levels of IR until the compiler emits assembly code for a specific CPU architecture and model in the very final phases of translation. If low or mid-level optimization works in C, then it will also work in C++ and Rust, as these optimizations are not tied to the language.
C and C++ depend on the same horrible and severely outdated concepts, such as Undefined Behavior. If you wrote “int x = 0; if(y) x = 1; else x = 2” compilers from 30 years ago could generate different code depending on whether the x=0 initialization was there or not. Modern compilers will completely eliminate the x=0 initialization (by virtue of SSA and Dead Code Elimination). Rust requires developers to initialize x 100% of the time, while C and C++ allow horrible code, where code paths exist that leave “x” uninitialized. This was considered “optimization” in the last century, but not anymore. Concepts still defended by C/C++ are now harmful; they depend on assumptions of tooling from 1980, not 2025.
C/C++ assumes that all developers are Formula 1 drivers and don’t need seatbelts, ABS or ESP, and developers must take care of security and safety. In reality, 100% of developers are far from perfect and are prone to accidents, that’s why seatbelts are required in cars. If you really need to take the seatbelt off in Rust, you have to write an unsafe block, which explicitly states that the author of such code should be held fully accountable if that code causes a CVE that compromises security.
So what if you save one or two assembly instructions in purely theoretical, “yeti-like” code? The code will not execute faster in a significant manner; however, everyone else pays billions of dollars per year due to the cost of these harmful pseudo-optimizations, like uninitialized memory or out-of-bounds access.
Until C/C++ proponents decide to modernize the language itself to prioritize modern problems, not problems of the last century, C/C++ will become COBOL-ized, and all innovation will be done in memory-safe languages like Rust. Legacy code will not die, but it will be put into maintenance mode.
Out-of-bounds accesses or other memory safety issues must remain in the past and be merely a historical footnote. Nobody learns intricacies of punch card readers anymore; this must be the same for memory safety issues. Arguments that developers must experience a SIGSEGV because of a stack overwrite are harmful and invalid. No architect will learn to use garbage materials or unsafe design methods by experiencing the killing of everyone inside a building they designed.
And they haven’t even tried Zig yet…
Zig is 100% non-starter for Google at this point. How can you guarantee that program that you write today would still be compileable 10 or 20 years down the road? Zig doesn’t even try to provide anything like such guarantee.
It’s a toy language at this point. Well, also a good cross-platform C compiler, I guess, but that’s also not needed for Android.
Programs written in Rust have equivalent (or even better, due to the lack of harmful Undefined Behavior baked into the language) performance to C/C++. There’s no magic in compilers. If someone thinks that if they write code like “x++” or “++x” and the compiler will translate it directly to “inc eax” (and that it would make a difference in the final assembly code), then their knowledge about compilers is based on last century practices.
Yes, that used to be the case in the ’90s, but now it’s far from reality. Modern compilers operate on SSA (Static Single Assignment), and C/C++/Rust/whatever code is translated into the same Intermediate Representation for any of these languages. In modern compilers, the “x++” can even be eliminated completely.
Then multiple transformation and optimization passes are performed on multiple levels of IR until the compiler emits assembly code for a specific CPU architecture and model in the very final phases of translation. If low or mid-level optimization works in C, then it will also work in C++ and Rust, as these optimizations are not tied to the language.
C and C++ depend on the same horrible and severely outdated concepts, such as Undefined Behavior. If you wrote “int x = 0; if(y) x = 1; else x = 2” compilers from 30 years ago could generate different code depending on whether the x=0 initialization was there or not. Modern compilers will completely eliminate the x=0 initialization (by virtue of SSA and Dead Code Elimination). Rust requires developers to initialize x 100% of the time, while C and C++ allow horrible code, where code paths exist that leave “x” uninitialized. This was considered “optimization” in the last century, but not anymore. Concepts still defended by C/C++ are now harmful; they depend on assumptions of tooling from 1980, not 2025.
C/C++ assumes that all developers are Formula 1 drivers and don’t need seatbelts, ABS or ESP, and developers must take care of security and safety. In reality, 100% of developers are far from perfect and are prone to accidents, that’s why seatbelts are required in cars. If you really need to take the seatbelt off in Rust, you have to write an unsafe block, which explicitly states that the author of such code should be held fully accountable if that code causes a CVE that compromises security.
So what if you save one or two assembly instructions in purely theoretical, “yeti-like” code? The code will not execute faster in a significant manner; however, everyone else pays billions of dollars per year due to the cost of these harmful pseudo-optimizations, like uninitialized memory or out-of-bounds access.
Until C/C++ proponents decide to modernize the language itself to prioritize modern problems, not problems of the last century, C/C++ will become COBOL-ized, and all innovation will be done in memory-safe languages like Rust. Legacy code will not die, but it will be put into maintenance mode.
Out-of-bounds accesses or other memory safety issues must remain in the past and be merely a historical footnote. Nobody learns intricacies of punch card readers anymore; this must be the same for memory safety issues. Arguments that developers must experience a SIGSEGV because of a stack overwrite are harmful and invalid. No architect will learn to use garbage materials or unsafe design methods by experiencing the killing of everyone inside a building they designed.
kangseong,
We can agree that syntax is irrelevant to the generated binary code and doesn’t matter to performance. Although the assumptions and semantics specified by the language behind the syntax can have a performance impact. Pointer aliases come to mind. Despite C’s reputation for performance, Fortran’s no alias assumption leads to better optimization than C does by default.
https://www.jucs.org/jucs_9_3/alias_verification_for_fortran/Nguyen_T_V_N.pdf
https://developers.redhat.com/blog/2020/06/02/the-joys-and-perils-of-c-and-c-aliasing-part-1
C string processing is deficient due to the nature of C strings forcing a sequential byte scan whereas most other languages have strings with explicit lengths. Knowing the length ahead of time lets compilers use faster memcpy functions that can process several bytes in parallel. Of course C++ offers modern string abstractions, but there are mountains of C code still using slower algorithms.
When it comes to compilers, C may have an advantage at the moment. I think the GNU compiler comes out ahead of the LLVM compiler used by rust. Intel’s C compiler is even better than GCC at auto-vectorization. Of course this is about compiler implementation and nothing specific about the language. If ICC supported rust, that could outperform GCC as well.
I agree. Undefined behavior is one of the biggest sins of C/C++.
Perhaps you can look at it this way, although I’m hesitant to use this metaphor because it promotes a misleading idea that safe=slow and unsafe=fast. The primary reason to use unsafe code in practice is to interface with unsafe languages, not to improve performance.
Many C fans routinely dismiss the costs of C’s faults, but we keep finding them everywhere. The next video is tangential to our discussion, but it acts as yet another example of a major C code base (ffmpeg) with faulty code.
https://www.youtube.com/watch?v=fxtnI407djY
“why everyone is mad at google”
I don’t blame the ffmpeg author any more than anyone else for the faults in C code, it’s an industry-wide problem. I think it’s going to keep happening as long as we stick with unsafe languages.
Punch cards were typically used with high level programming languages like fortran/algol/cobol/simula. Despite being older and obviously hard to input, arguably these punchcard languages were safer than the C language. For better or worse, C and unix became conjoined and would rise together.. I view unix as a very positive contribution but C would end up subjecting the world to so many faults that I think it was a mistake. Still, it’s far easier to say this in hindsight. It’s hard to fault them for not knowing better at the dawn of computing.
By virtue of its flexibility and breadth of application, C ended up growing the total size of the software industry by an incredible amount. I think that your last statements about the powers of hindsight are really the most even-handed viewpoint to apply to this argument. C’s particular mix of attributes contributed mightily to the growth of computing… but that doesn’t mean that we should cling to any aspect of “The C Way” that has been proven to be outmoded. As for C++… ugh. Even less so.
Kuraegomon,
It was possible for C to become the backbone of software because that’s clearly what happened, but I don’t really think C was the impetus behind the growth in computing and software. C was in the right place at the right time. but the industry would have grown with or without it. And I argue that unix selecting C displaced the opportunities for better language candidates. They weren’t thinking about computing decades into the future and didn’t have the benefit of knowing how things would turn out, but if we could somehow send a message back telling them their language would become the backbone of computing and reveal the many ways it’s become problematic, things could have turned out better decades later.
It’s also our fault for not switching sooner, but much like a building’s foundation, it becomes much harder to change later after it’s been set. That’s what the software industry is facing now, Many in the industry readily acknowledge the problems posed by C, but even among those who do the notion of changing the foundation of software can be unpalatable because of the effort needed to fix it now. The right time to fix this would have been decades ago at the beginning, but of course that’s wishful thinking.
The fact that exact same people invented abomination called Go tells that your time machine wouldn’t have helped one jot — even if you would have sent working compilers on it.
C is horrible language, but it did things that were really important at the time: quick compile times, small memory footprint for both compiler and compiled program… we had to go through something like that, we could have avoided some crazy mistakes like null-terminated strings, but the whole “we don’t want to fix bugs in our program, we just want it to run” attitude would have won, anyway (reference to the quite from “The Billion Dollar mistake” video… watch it: https://www.youtube.com/watch?v=ybrQvs4x0Ps )
C wasn’t inevitable… most horrible sins that C included… these were impossible to avoid.
I wouldn’t call it impossible, given that there were languages avoiding many of C’s problems in that timeframe, However C did win and so the point is mute. We have to deal with the problems we have and not the ones we wish we had, haha.
Where have these languages been at that time? On 8-bit micros? C have become ubiquitous when microcontrollers (all early 8 bit CPUs were positioned as microcontrollers, their creators had no idea someone would make computer out of them, even if today we wouldn’t classify them as microcontrollers) upturned the IT world.
Pascal was marginally better than C, but even it was crippled on these devices: sum types were removed and replaced with unions, bounds checking wasn’t mandatory, etc.
More advanced languages had zero chance of succeeding.
zde,
Before pascal there was simula, but yeah the pascal/ada lineage of languages definitely had more of an academic background than C, which was quickly hacked together and wasn’t really well planned. I think Ada is a decent language, these days sparc extensions have been added to make it safe, but the licensing killed adoption in early years and it never recovered. Once everything used C for applications, that was the end.
I actually think C would have had zero chance of succeeding if a different language was chosen for unix. Again though it’s all mute, history went the way it did.
It would be great if they knew the shortcomings of C. But the same could be said of Unix as a whole: Plan 9 is what Bell labs realized with twenty years of experience what Unix should have been.
With Zig available in 9front we finally have the opportunity to re-write the correct OS in the correct language.
Squizzler,
That’s a good point, Unix is by no means perfect and Plan 9 does offer several improvements. But I think relative to each other, the harms caused by C in modern software eclipses all harms caused by unix itself. IMHO many unix principals are still sound and in some way we can view new projects like Plan9 as a way to apply those principals more consistently.
BTW, I like what Plan9 does under the hood, but some of the stuff they do on the desktop is weird and probably should be left to a different project because it likely causes some people to write off plan9 as a whole.
One of these days I’m going to have to give Zig a try.
Alfman,
It is literally impossible to build a useful system programming language without undefined behavior. Our current hardware architecture does not allow it.
But some languages like Rust separate the “I have no idea what this will do” stuff from a subset of the language and has a safe / unsafe barrier. (In the safe barrier they can prove things work to the spec, as long as the spec is true, there are no compiler bugs, and hardware is reliable)
C++ is just one large “unsafe” block.
(Which makes it extremely alluring for systems development. It trusts the developer completely, for better or worse).
sukru,
I’m having trouble understanding what you mean. Is this a reference to integer and buffer overflow? Some languages do define these behaviors. Is this a reference to out of memory errors that can happen at any time? I would argue the behavior of exceptional cases can still be well defined by the language even if the programmer wasn’t specifically expecting them. Is this a reference to multithreading race conditions? This one is notoriously tough to solve, but languages like rust have have made progress here too.
I can think up obtuse examples whereby a language can’t define behavior, such as in the case where a hacker as taken over the OS and has access to change arbitrary memory in your process. Can a language guarantee well defined behavior under such a case? Obviously the answer has to be “no”, but when we’re talking about well defined behavior in programming languages, we’re usually giving them the benefit of doubt that the hardware and OS are functioning normally because if these have been compromised then of course all bets are off.
So I guess I’d like an example to understand what you mean.
Agreed.
I concede not everyone agrees on this, but as a programmer who does systems development myself I disagree with that and would point out that low level code benefits from safety checks just as much as high level code. Working in a systems context doesn’t fundamentally change the principals of software correctness. IMHO the industry needs to dispel the notion that safe languages only benefit high level application programming. There are times we need to use “unsafe”, but marking the entire project as “unsafe” is much more a reflection of the programmer’s attitude towards safety than an assertion that safety principals are irrelevant to systems programming.
Alfman,
No, these are not even issues for modern C++.
Many of the “undefined” behaviors for C++ can be made “well defined” with set of macros, compiler options, and static code analysis.
I’m talking about fundamental undefined behaviors emerging from system programming.
I’m more concerned about the hardware not mapping 1:1 to higher level languages. For example, the very simple looking:
Seems straightforward. It will depend on only the size of a, and copy one value from the array to x. Let’s assume these are int32.
It’s assembly counterpart:
Will usually do the same, but also make no promises. The “undefined behavior” depends on how the underlying memory is mapped, segments are aligned, and in many cases how the I/O device register are set up (if this is mapped to a PCIe region), among other things.
Not to mention side effects, like cache coherency, potential clearing / setting of flags, or changing device states.
You can solve some (or many) of these issues with tight integration with the OS, and tracking every pointer. And that is your safe region.
When you step out of it, for many parts in systems programming, you are by definition in unsafe regions.
The difference between Rust and C++ is, Rust enforces those safe regions… with your help (again assumes promises are held), while C++ leaves you on your own devices.
sukru,
I’m still not sure that I follow. Why does it matter if there’s a 1:1 mapping between a language and the hardware? Obviously the compiler writer has to worry about that, but thanks to turning completeness it’s always going to be possible. Even a terribly obtuse architecture can be used to implement a well defined language. Consider the turning machine itself lacks 1:1 mapping with most modern language features.. Still, every single programming language in existence can technically be implemented as a turning machine. I can see valid objections to why we might not want to do this, but nevertheless I’m not seeing anything that makes it “impossible”.
Instead of asking if it’s “possible”, a better question may be to ask whether it’s “reasonable”. Since typical every day programming languages share extremely similar mathematical & logical building blocks, whether it’s C, rust, or even c# and javascript, mapping these to hardware isn’t typically seen as an insurmountable problem.
These things are all abstracted by the OS and/or language. The fact that memory segments can be chopped up behind the scenes and even opcodes can be broken up into micro-ops doesn’t change the fact that hardware, operating systems, and languages can present features as a well defined abstraction.
In a way each layer can be considered a contract. The hardware has a contract for what each instruction will do and the OS & programming languages & software are allowed to rely on this contract. This allows us to built up from lower foundations. Of course these “contracts” could be broken, and we have examples where it’s happened. A notorious one being the old pentium fdiv bug.
https://en.wikipedia.org/wiki/Pentium_FDIV_bug
When this happens, things built on top can and will break because the contracts have been violated, but in such instances the programming language isn’t considered faulty. It’s understood that programming language correctness depends on the correctness of all the layers underneath it. Still though, despite this weakness, it does not follow that we should abandon our attempts at building robust programming languages.
Things like flags are already well behaved and programming languages can reliably use them to pass on well behaved behavior to the program. Like integer overflow flags, interrupt flags.. Even flags that programming languages don’t often use are still well behaved. Sure hypothetically if a direction flag were to spontaneously flip, I agree it could break things, but I’d counter that’s a hardware or OS fault that needs to be fixed and not a refutation of languages with well defined behaviors.
There are countless examples of IO errors, which are rarely expected and can happen at any time. “Well defined” doesn’t mean the error can’t happen, but it means that the language must handle the errors consistently in a well defined way (I’m sure you’ve also seen a lot of C code keeps executing an invalid path since it doesn’t check for errors).
The only case that I can think of where a language can not possibly defined is one where the underlying contracts have themselves been broken such no programming logic can continue under well defined behavior. Bad memory could be an example of this, but it’s a hardware problem and we have hardware mitigations like ECC ram to help with it.
Yes, I agree with this by definition. However I don’t think there are that many situations that mandate unsafe code be used, even for system programming. There will be some places unsafe sections are necessary, but the kernel is a big piece of software, most of which can be built using safe code in principal.
Alfman,
Again, we cannot fix something that is fundamentally unfixable. We can be okay with some amount of undefined behavior, and having that documented is necessary. However we can never completely get rid of it.
Basically,
For a application development language, we can have as many constraints and formal proofs as we want. A runtime like .Net or Java will even make it even more structured.
For a system development language, we can never avoid undefined behaviors as we have to “touch the metal”. We can either try to minimize them (by separating higher level logic into “safe” blocks), or ignore them at our peril.
Well… we can have a separate discussion on what goes into the system layer, which goes on upper layers (hint: I prefer micro kernel designs)
sukru,
I’m still having a lot of trouble understanding why you say languages themselves can’t be well defined. I can concede that hardware can exhibit faults and by extension we can never guarantee 100% software execution. But that’s not the fault of the computer science. Would you argue that because someone cannot physically draw a perfect circle that mathematicians must refrain from reasoning about the properties of perfect circles? I’d say the same is true in CS.
I’m not altogether opposed to using unsafe blocks to interact with low level hardware interfaces when it makes sense to, but at the same time I’m not convinced that just because something is low level that it rules out the use of safe language abstractions, even in drivers. In principal a safe kernel can provide safe abstractions to generically handle most hardware resources: IO ports, memory mapped addresses, DMA, and interrupts, etc. The abstractions might themselves require some unsafe sections, but then if you can write thousands of drivers on top of these safe abstractions, it means the drivers themselves won’t need unsafe code in every driver. Running a kernel with mostly safe code could go a long way in providing assurances that the kernel and drivers themselves don’t have memory faults. Keeping unsafe blocks inside of a relatively small number of abstractions lets us audit far easier than if the entire code base was “unsafe”.
I for one don’t think the value of safe languages diminishes in system level code, but I understand not everyone will agree. We’re probably going to end up disagreeing 🙂
Alfman,
Yes, we might disagree and that is perfectly fine.
The language itself can be well defined. In fact there are many provable ones in practice.
No, I am not worried about a cosmic ray hitting the CPU at the wrong angle and corrupting an internal register (even though that also exist)
My concern is more fundamental.
The language and hardware work with very different set of abstractions and assumptions.
None of the languages we have, save assembly, have a perfect 1:1 view of the underlying hardware. (This includes C, but that is one of the closest ones)
That means any safe block you abstract DMA is just a lie. A good and useful lie, as it works most of the time. But it does not change the fundamental undecidability of the hardware instructions mapping to that presumable “safe” region.
sukru,
You’ve been focusing on this 1:1 mapping, but I don’t see it matters? Nearly all languages share the same primative types, logic, and mathematical operations. And thanks to turing completeness, all languages, including assembly, can be expressed through one another. Languages can emulate capabilities that the hardware doesn’t natively support. You could create a well defined language that supports variables with arbitrary precision math. Clearly it would not have a perfect 1:1 mapping to the hardware, but that doesn’t matter. The lack of a 1:1 mapping between language and hardware doesn’t prevent the language from being well defined.
In terms of writing device drivers, like PCI devices, most languages already have the capability to read/write memory mapped devices. It turns out that neither C nor Rust have a native language feature to read/write ports, but it doesn’t matter because they can just wrap assembly code to do it. This only has to be done once and from there on out drivers can call the inp and outp functions without requiring all drivers to individually dip into unsafe code. This can be done with memory mapped IO too. For their part, PCI devices couldn’t care less about the programming language used or even that the host is x86 at all. A language being “close to hardware” matters not to PCI/USB hardware.
The point being we should pick the language that works best for *our* needs because the hardware itself really isn’t going to care at all. IMHO it should be a safe language, but everyone can vote differently 🙂
For what it’s worth, despite my stated opinions here, I confess I still use C a lot. I still find it to be the path of least resistance because for better or worse it’s easier to use the same language everyone else is using. When using rust I find that an awful lot of work is just wrapping C code/interfaces so that I can write stuff in rust 🙁 Maybe if this process could be automated it would lessen the burden of going against the flow.
Not necessarily. The kernel could create a safe DMA abstraction that both guarantees the memory range is valid and ensures remains allocated until the assignment is cleared.
The point I’m trying to get at isn’t that the code creating this abstraction is safe, but the code using it can be. A safe abstraction can be reused by thousands of drivers and there is value in doing so.
Alfman,
Yes, let’s hope the IOMMU was set up correctly, and the device PCI RAM actually has been ready to read the writes that we did, and it has no side effects on that particular device.
Anything in the “safe” regions are as good as application code. Anything that actually does system level programming is “unsafe” by definition, and will touch metal.
(Driver + Firmware + Low level kernel structures)
Alfman,
Maybe this would be helpful.
We had Lisp machines in the past. Today we no machines for any mainstream language
When you were using a Lisp machine, you would be sure that the high level abstractions map correctly to the physical reality of the machine that you used.
Today, there is no such Rust machine, Swift machine, or even a C machine (C still requires an ABI to close holes, and might even change from compiler to compiler).
Hence what we do in the abstract, mathematical, and for some languages, in some contexts, formally provable land…
Does not actually fit to physical reality without an abstraction layer.
And that layer is what we naturally consider “unsafe”.
sukru,
In terms of what “safe” means in memory safe languages, there’s no fundamental reason we must rule them out for low level system programming. Granted it’s a more privileged address space, but even so we’re still talking about the same problems and solutions to unsafe language faults.
System programmers are resistant to change and many aren’t ready to replace C with rust. I can accept and live with that. In principal though using safe languages to protect from faults in application code but not using safe languages to protect from those very same faults in system code is an arbitrary line that fits pre-existing language pigeonholing rather than applying a meaningful division based on different fault paradigms actually existing in privileged and unprivileged address spaces.
Yes, programming languages (and operating systems, libraries, etc) create these abstractions. And I also agree that without safe language abstractions, we are left to use either assembly or unsafe language abstractions. I don’t know if we can agree more than this, but I’ll ask is it possible we agree that drivers could be written in either safe or unsafe language abstractions?
Alfman,
Of course you can write drivers in safe abstractions. And this is not new. Microkernels, like NT had them for decades (HAL in case of NT, which was even architecture agnostic, the same driver would run on MIPS, Alpha, or x86. Unfortunately they went hybrid for performance reasons in Vista+)
sukru,
Well, that’s mostly what I’ve been trying to say, albeit microkernels focus on isolation whereas memory safe languages focus on code correctness. They’re not exclusive, both can improve different aspects of driver safety.
There are certainly pros and cons to consider with microkernels. That’s a controversy not only in windows circles but linux circles too.
Context switches have gotten cheaper thanks to intel engineers enabling CPU speculation across syscalls. But they were also severely punished when the meltdown vulnerability revealed this optimization leaked execution path information across addresses spaces. Frankly it’s easy to see why. The solution was to reintroduce speculation boundaries, which made context switches more expensive again. :-/
While the information leak is real, sometimes I muse over the relative importance of performance and security. Obviously there are times security is very important, but for the majority of the time in many applications there isn’t really valuable information to be leaked even if the exploit were successful. This ostensibly means the OS and hardware could offer APIs to opportunistically switch between security and performance. But this makes software engineering that much more complicated and what are the odds of software developers actually get things right and not introducing more exploits, haha.
For the Unix to succeed it needed to be able to run on 16 bit micros, including such things as PC/XT or TRS-80 Model 16B (the most popular Unix system of year 1983, I’m not making it up!).
Any language you may squeeze in these would have been very similarly limited.
Maybe, just maybe, we would have ASCIIZ strings, but everything else was dictated by the need to fit into these tiny devices.
zde,
Yes, It wasn’t just C, it was normal for programming language authors to target 16 bit hardware at the time.
For example: https://en.wikipedia.org/wiki/Absoft
Funnily enough 16bit programming was still being done well into the 90s! I’m not really sure about unix per say, but certainly DOS compilers were still able to target 16bit segments.. Bloat hadn’t yet kicked into high gear. It would be interesting to compare 16bit compilers of the day.
Since rust performs static analysis at compile time, it ought to be possible to write a rust compiler that targets 16 bit environments efficiently. How funny would that be 🙂
Of course these days modern compilers would take a hello world program written in the 1980s and output static binaries that overflow DOS’s entire 640k conventional memory, much less a 64k segment! Switching to 64bit and standard library bloat over the years will do that, haha.
zde,
That is why Microsoft and Borland became successful as well. They had operating system and compilers that fit into a floppy with an IDE to develop applications for common 16-bit systems.
And as “the space shuttle is designed based on the rear size of roman horse carried chariots”, C dictated the design of all software and hardware systems later on.
(We basically had 3 very different designs: C, SmallTalk, and LISP. Guess which completely dominated the hardware design)
sukru,
That’s true, although I don’t think this was particularly surprising given that 16 bit architectures were common. Targeting them was just sensible.
Indeed. C continues to shape software to this day. It became the standard for software interfaces, not just for C programs, but a common denominator for all languages. IMHO one of the more egregious limitations with C is global namespaces. With a bit more foresight it would not have been difficult to add the syntactic sugar to support namespaces. But at the time the need probably didn’t occur to the authors because they were such a small team writing small utilities and were not thinking about the future of scalable software. In any case, it continues to impede programming interfaces decades later. Even languages that support namespaces (including C++) give them up to interoperate with C interfaces, which is ironically a use case where namespaces would be particularly useful. It’s affecting a project I am currently working on.
I’m always torn whether to fault them for this or not because it’s easy to be critical in hindsight, especially for things that would have been technologically easy to address, but at the time they didn’t know better. In many ways this parallels programming language mistakes made with PHP starting out as an amateur language; nobody knew would eventually run fortune 500 websites.
I take it you are talking about language specific hardware? Yeah that’s a hard sell. Obviously languages that target hardware won out over hardware that target languages. These days hardware and languages share enough common building blocks that language portability is quite effective and there’s zero interest in language specific hardware.
Alfman,
My argument was beyond “C influenced other languages”
C represents the “classic” imperative style programming
Smalltalk represents “object oriented” programming (not Java style, but JavaScript. Unfortunately they have given up their heritage for TypeScript)
Lisp represents… obviously functional programming.
We had Lisp machines in the past. The actual hardware design tailored for that group of languages.
Today C has influence basically all modern hardware. It no longer is the “best language that fits PDP-11”, but it is the language all ISAs are built around.
sukru,
Well, why would hardware favor imperative C versus OOP or functional or anything else? All these programming styles make use the same building blocks under the hood. An OOP class method that acts on an object is functionally identical to a global function with an extra “this” parameter to the object.
I wrote a small program to verify the assembly output. Notice that using C++ OOP classes with constructors and methods outputs identical assembly to C structs with global functions…
Edit: I’m using C strings and stdio in both cases and not iostream to highlight that OOP in and of itself doesn’t change the output. Obviously there are differences between C and C++ libraries.
The OOP code…
The Non-OOP code…
We can have OOP, or change the language entirely, it’s all the same to the CPU, right? Can you think of any x86 or ARM features that are tied to C and would not have been useful with other languages? I’m not sure which CPU features this would be.
The one that to mind is tagged-memory, to catch memory faults in unsafe languages.
https://community.intel.com/t5/Blogs/Tech-Innovation/open-intel/ChkTag-x86-Memory-Safety/post/1721490
So maybe we can say this hardware feature actually is being built because of C, since safe languages don’t share this need. But maybe we can agree this hardware feature isn’t one that C should be proud of.
Alfman,
You are still thinking in “C” mode. Of course the current C++ OOP will produce the same output.
I specifically called out Smalltalk which is prototypes and message based. It does not have classes or methods. Same with JavaScript or Squeak. Very few languages actually have that.
–> TAGS in pointers (64 + 8 bits for example)
–>–> dirty bits, hardware write barriers (hardware assisted GC)
–> SEND instead of CALL/JMP
–> Content Addressed Memory for lookups (like the cache, but programmable)
Recent RISC-V / ARM including Apple M series add some of these, but I’m not fully privy to details.
Okay, researching this I learned something new:
“MTE (Memory Tagging Extension): This allows “coloring” memory. If the pointer’s tag doesn’t match the memory’s tag, the hardware throws a fault. This effectively kills buffer overflows and use-after-free bugs at the silicon level.”
(AI summary)
Wow, we actually have silicon support for killing most of the memory bugs. That is pretty new for me.
Update:
The rabbit hole deepens. I really need to read more.
RISC-V also has “CHERI extensions” which seem to do similar things.
40 years late, but still better than never.
sukru,
Then I am thrown off by calling it “C” mode because it seems like the majority of languages also fit under that umbrella. So by using “C” you’re including other languages like C++, pascal, and even quickbasic that can compile down to the same target as C? I have to confess I find this terminology confusing.
It’s a property of languages that aren’t designed to be compiled at all, the ability to perform stuff at runtime that others do at compile time enables some different capabilities for sure, and I’ll grant you that it’s harder to turn this into compiled code, at least without JIT, That said, the way I use javascript is the same way I’d use a compiled language because IMHO just because a feature exists doesn’t mean it’s a good practice to use it, haha.
I find C# to be the language that best reflects what OOP should do. Not too much and not too little, I find it just works well in practice. Async is a game changer. I wish it could do non-GC code though since GC rules it out for low level/system programming.
I hear what you’re saying, given a language so prone to overflows and invalid memory operations, we should be able to detect this corruption when it happens. But this is akin to treating the symptom rather than the cause, clearly inferior to languages that do not produce memory faults to begin with. Tagging is most useful for debugging unsafe languages, because it can halt execution the moment a fault happens. But unfortunately latent faults are still going to lurk in code and faulty programs are still going to crash for users.
Alfman,
I’d recommend checking Objective C.
It is a compiled language that is based on SmallTalk. It uses message passing, and dynamic dispatch, and much more powerful than people realize.
There are no errors or symptoms. Sending an any message to any object is perfectly valid, even sending null, or sending a message to null.
(I say C, because it literally influenced not only the design of other languages, but also hardware. Yes, Pascal, too. Pascal used to be more of a lightweight ADA, but over time it became a chatty C clone)
C# is a better Java, but not the epitome of OOP.
It also can run as a system language without GC
stackallock is a first class operation, along with perfectly fine value based struct types. One can also use pre-allocated buffers and avoid all GC, or plain old native interop and forego .net-isms.
Not to mention recent Span[T} and Memory[T]… which works directly with preallocated regions.
And yes, there is also an operating system written in C# (Cosmos)
sukru,
I still don’t follow why you are saying this? What specific hardware features would we not have if other languages became dominant instead? I’m hard pressed to come up with features of x86/ARM/etc that only exist because of C? Can you come up with any?
Earlier I came up with memory tagging, however I doubt this is what you had in mind.
I wouldn’t call these languages “clones”. But according to wikipedia Pascal is two years older than C, so C would be the clone of pascal rather than visa versa. Both of these were borrowing from even earlier languages alogol, simula, b, fortran, My own view is that software innovation is a collective effort with foundational ideas likely being independently invented because that’s what happens when we follow logic and math.
It seems to me that modern devs have forgotten that Pascal and it’s family tree were a viable and popular alternative to C in the early years. Realistically C might have never been able to catch up on it’s merits if not this critical factor: unix becoming wildly successful and bringing C along for the ride.
https://en.wikipedia.org/wiki/Pascal_(programming_language)
Obviously unix went with C, but it could have just as easily been something else and that’s what we’d be using today.
sukru,
Yeah, you can avoid the operations that would cause garbage collection, but that’s akin to programming in handcuffs, putting unnatural restrictions on usage and “freeing” objects to dead object pools. Technically you’ll have stopped GC, but it’s just not the same as a true non-GC language.
Even as a fan of C# programming, I’d still call this a con.
Alfman,
Because all modern CPUs were optimized for running C code fast, and barely nothing else.
They added features C likes (branch prediction, pipelining) and removed / deprioritized things C don’t use.
Today they are rediscovering alternate uses of silicon space.
I had touched some of them above, look for this comment:
That is true, but misleading. Today if you compare “pascal vs c” or even “standard pascal vs delphi/lazarus dialect” you don’t get useful information, you should look for:
something like https://www.researchgate.net/publication/220459730_A_Comparison_of_Pascal_and_Ada
Sorry… feels like old habits, and giving “homework”, but I suggest these would be helpful in understanding the co-evolution of hardware and software.
Also.
Custom silicon is making a come back.
Look up why Google’s Gemini 3.0 was developed with TPU (instead of nvidia GPUs). It is more than a marketing gimmick.
And maybe, look this up as well:
sukru,
CPU architectures can run a whole gamut of conventional programming languages, right? So what is the rational for attributing CPU architectures to C specifically? Is it simply because C’s popularity grants it bragging rights over others and winners get to write history in their own image? Or is it possible to identify specific ways in which C was a meaningful differentiator to a CPU architectures that would not have happened in the absence of C? Because if we can’t identify these specifics, then it seems to be a weak point for the theory that all CPU architectures are based on C.
I agree CPUs have evolved including the ways you mention, but I’m hard pressed to identify C specific developments.
I agree that it’s hypothetically possible to create language specific hardware, but do you think it unreasonable for me to suggest that x86 and ARM are language agnostic architectures? C is designed to work on generic hardware, rather than requiring special hardware designed for C. This is good IMHO, but it shares this trait with many other languages.
I do agree that hardware and software evolved together, it’s natural that they would. x86 added floating point coprocessor and C used it, but so did other languages. C benefited from caching and super-scalar architectures, but so did other languages. C made the transition to 32bit and then 64bit after, but so did the other languages. The C compiler would eventually generate vectored code to take advantage of AVX, but so did other languages. So I’m not disagreeing with your point that hardware and software evolved together, I know they did. However there’s nothing to indicate that C was uniquely special in this regard.
Specialized hardware can beat out generic hardware. It’s also dangerous to be dependent on someone else’s proprietary stack. Bitcoin miners are another example of specialized hardware beating out GPGPU hardware. Apple and samsung do the same thing. The vast majority of companies can’t afford custom silicon, but I agree we would see more if they could. I would too if I had the money 🙂
I found lots of results for this function. This one goes over implementation, although I’m not really sure what it is you want me to see?
https://deviltux.thedev.id/notes/dissecting-objc-runtime-on-arm64/
Alfman,
No, they are pretty much based on C.
Living in an ocean, fish do not recognize what water is. This kind of situation.
Why are pointers raw integers, but don’t have tags? (finally making a comeback)
Why don’t we have memory barrier instructions (for actual GC)
Why don’t we have “send” instead of “call” (for actual OOP)
And why don’t we have content addressable memory (hashtables — TPU have those)
Those are good example, but are very niche.
Again, among the three major language designs: imperative (C), object oriented (smalltalk), functional (lisp) only two got CPU architectures, and only C survived — until recently.
That looks like a good one, but does not seem to mention specialized ops in newer ARM devices.
Those are unfortunately spread out on multiple articles. And much harder to locate than I assumed (why don’t people touch this more often)
For example:
https://news.ycombinator.com/item?id=25204527 — barely mentions TBI which is very important. “Top Byte Ignore” basically leaves 8 bits from each pointer.
Which is now an ARM standard extension used in Android as well (for tagged memory)
https://source.android.com/docs/security/test/tagged-pointers
It can also accelerate reference counting.
https://www.linaro.org/blog/top-byte-ignore-for-fun-and-memory-savings/
Bonus:
https://developer.apple.com/documentation/security/preparing-your-app-to-work-with-pointer-authentication
“Pointer authentication”, causes a TRAP when people try hacking pointers
sukru,
The issue I have is that none of the examples provided, including the C pointers you cite here, result in new architectural features wouldn’t exist without C. Virtually every programming language needs the same hardware feature. So with no concrete examples of features where C made a difference, I think we have to agree to disagree.
Trying to convince me that hardware did not evolve with language specific functionality is what I’ve been trying to convince you of 🙂
I’d say what survived is generic hardware. that provides the computer science building blocks that nearly all languages use. Also object oriented languages aren’t dead but thriving right now.
Yeah memory tagging has been around for a long time, even before CPUs had tagging features. I’m not that excited by it though because it’s only a probabilistic mechanism for detecting memory faults, not preventing them. Can you think of other applications?
Alfman,
I guess Lisp machines were never a thing.
sukru,
Ah the sarcasm’s coming through, haha. The existence of lisp machines wasn’t in question, but IMHO evolution favored more generic architectures over language specific ones.
For a chuckle I asked ChatGPT to close to the topic in a friendly & balanced way…
You can have the last word if you want to do the same 🙂
Alfman.
I really do no carry either way, but … for amusement… Gemini was much chattier
I could really start a blog or something with this 🙂
Minor correction,
“Tagged” pointers do not need to be 64 (plus) N bits, but rather can be 64 (minus) N bits.
In other words 48 bits is more than enough for everyone! (256 TB), or we can even to 56 bits (64 PB), and leave the upper 8 – 16 bits for the tag (or sometimes the size!)
And if / when we actually reach those limits, we can probably go to 128 bit computing with registers having sufficient headroom for even larger tags (96 bits is enough for everyone!)
I wonder how you manage to cram so many mistakes and myths in one blog post. Concept of “Undefined Behavior” is absolutely critical for most languages, including Rust — only modern languages make you specify where exactly you want it… but it’s absolutely there and it’s absolutely unavoidable. Uninitialized variables are **do** exist in Rust and standard library uses them extensively, otherwise many performance tricks would have been impossible. And, of course, Rust doesn’t protect you from stack overflow at all…
The only relatively correct observation is about the need to separate Formula 1 drivers from the rest… that’s true but the brilliance of Rust comes precisely from understanding that Formula 1 drivers do have a niche… a 4% niche. Both pretending they are not needed (like languages like C# or Java are doing) and pretending that 100% of developers are like them doesn’t work…
zde,
I think you are spot on. (Ah, I’m speaking like ChatGPT, but yes the praise here is real)
The main problem with C++ is lack of proper knowledge. And that includes long term professionals (there are even cases where people like Bjarne Stroustrup were humbled).
However many undergraduate courses “teach” C++ in 4 months as a “C with Objects” (or rather Java with less security), and that causes that 96% to write disastrous code.
The trick is always learning, and improving yourself, and being open to “I don’t know C++” even though I have ~20 years professional experience with it.
This can’t be right. Everyone knows AI is the only thing that can lead to efficient bug free code!