I've always found this interesting because nothing about the appearance of a snake has ever bothered me, unsettled me, or made me fearful. They actually look very neat to me, and the tiny snakes I've had the fortune of holding were very fun to feel slithering about in my hands.
Cockroaches on the hand, not scary at all, but I feel disgusted by them.
And large spiders, extremely scary to me, instant fear response.
It's the exact same for me. The spiders are by far the most visceral fear response, especially if a gruesomely detailed photo pops up on my phone.
Smaller spiders scared me when I was younger, but I have overcome that phobia significantly. Large, hairy, distinctly arthropodic spiders, though...? Yuck.
Question for both you and GPP; is this fear limited to real life depictions, or basically anything? E.g, if you ever played Skyrim or a game with spider-like enemies does it have the same effect as a real spider?
Answers I've seen to this question tend to vary wildly.
Spider-fear has never been triggered by fictional spiders for me. Very few works ever bother getting the face and body right though. 8 legs alone are not scary for me, the fangs and eyes and color patterns and the sneaky movement and webs are scary.
I'm not terribly afraid of real spiders though. Hairy crawling spiders like wolf spiders and tarantulas don't really bother me at all. It's the ones with the big web-spinning butts that dangle and drop down from above that make me go straight into fight-or-flight.
I'm also really afraid of snakes, but spiders are okay.
Movies with snakes are quite painful to watch too, and I'm very uncomfortable with snakes in video games, but at least I have some control (compared to TV) so it's a significantly better experience
It’s weird that I’m the opposite. Spiders of any size have absolutely no effect on me. But snakes trigger some sort of innate response. I wonder if it’s tied to geographic origins of our ancestors?
I'd guess it's due to some kind of imprinting during childhood, similar to taste. The widespread prevalence of irrational phobias and methods for curing them certainly suggest to my untrained eye a learned behavior rather than innate.
I have no idea. I do not fear any of them, but I would fear some in real life were they near me, but only because I know they might be able to kill me.
Cockroaches are just, like someone else said, disgusting to me, especially if they are at home. If they are outside I could not be bothered.
I grew up with children and people in Northern Australia that had zero fear of snakes and spiders with plenty of exposure to both.
When I was 13 a friend of my sister, a large imposing Torres Strait Islander girl, visited and saw a cat for the very first time and screamed fit to break glass while jumping back to break the wall panel and up onto the couch.
This was someone comfortable handling large live mud crabs on the floor, gutting fish, handling snakes and killing them, etc.
Not a system programmer -- at this point, does C hold any significant advantage over Rust? Is it inevitable that everything written in C is going to be gradually converted to safer languages?
C currently remains the language of system ABIs, and there remains functionality that C can express that Rust cannot (principally bitfields).
Furthermore, in terms of extensions to the language to support more obtuse architecture, Rust has made a couple of decisions that make it hard for some of those architectures to be supported well. For example, Rust has decided that the array index type, the object size type, and the pointer size type are all the same type, which is not the case for a couple of architectures; it's also the case that things like segmented pointers don't really work in Rust (of course, they barely work in C, but barely is more than nothing).
Can you expand on bitfields? There’s crates that implement bitfield structs via macros so while not being baked into the language I’m not sure what in practice Rust isn’t able to do on that front.
Now, try and use two or more libraries that expose data structures with bitfields, and they have all chosen different crates for this (or even the same crate but different, non-ABI-compatible-versions of it).
There's a ton of standardization work that really should be done before these are safe for library APIs. Mostly fine to just write an application that uses one of these crates though.
I'm not a rust or systems programmer but I think it meant that as an ABI or foreign function interface bitfields are not stable or not intuitive to use, as they can't be declared granularily enough.
C's bit-fields ABI isn't great either. In particular, the order of allocation of bit-fields within a unit and alignment of non-bit-field structure members are implementation defined (6.7.2.1). And bit-fields of types other than `_Bool`, `signed int` and `unsigned int` are extensions to the standard, so that somewhat limits what types can have bitfields.
Dynamic linking is not something you do in general in Rust. It's possible, but the compiler currently does not guarantee a stable ABI so it's not something one generally does.
That first sentence though. Bitfields and ABI alongside each other.
Bitfield packing rules get pretty wild. Sure the user facing API in the language is convenient, but the ABI it produces is terrible (particularly in evolution).
I would like a revision to bitfields and structs to make them behave the way a programmer things, with the compiler free to suggest changes which optimize the layout. As well as some flag that indicates the compiler should not, it's a finalized structure.
I'm genuinely surprised that usize <=> pointer convertibility exists. Even Go has different types for pointer-width integers (uintptr) and sizes of things (int/uint). I can only guess that Rust's choice was seen as a harmless simplification at the time. Is it something that can be fixed with editions? My guess is no, or at least not easily.
There is a cost to having multiple language-level types that represent the exact same set of values, as C has (and is really noticeable in C++). Rust made an early, fairly explicit decision that a) usize is a distinct fundamental type from the other types, and not merely a target-specific typedef, and b) not to introduce more types for things like uindex or uaddr or uptr, which are the same as usize on nearly every platform.
Rust worded in its initial guarantee that usize was sufficient to roundtrip a pointer (making it effectively uptr), and there remains concern among several of the maintainers about breaking that guarantee, despite the fact that people on the only target that would be affected basically saying they'd rather see that guarantee broken. Sort of the more fundamental problem is that many crates are perfectly happy opting out of compiling for weirder platform--I've designed some stuff that relies on 64-bit system properties, and I'd rather like to have the ability to say "no compile for you on platform where usize-is-not-u64" and get impl From<usize> for u64 and impl From<u64> for usize. If you've got something like that, it also provides a neat way to say "I don't want to opt out of [or into] compiling for usize≠uptr" and keeping backwards compatibility.
> ...not to introduce more types for things like uindex or uaddr or uptr, which are the same as usize on nearly every platform. ... there remains concern among several of the maintainers about breaking that guarantee, despite the fact that people on the only target that would be affected basically saying they'd rather see that guarantee broken.
The proper approach to resolving this in an elegant way is to make the guarantee target-dependent. Require all depended-upon crates to acknowledge that usize might differ from uptr in order to unlock building for "exotic" architectures, much like how no-std works today. That way "nearly every platform" can still rely on the guarantee with no rise in complexity.
I brought up Go because it was designed around the same time and, while it gets a lot of flack for some of its other design decisions, this particular one seems prescient. However, I would be remiss if I gave the impression that the reasoning behind the decision was anticipation of some yet unseen future; the reality was that int and uint (which are not aliases for sized intN or uintN) were not initially the same as ptrdiff_t and size_t (respectively) on all platforms. Early versions of Go for 64-bit systems had 32-bit int and uint, so naturally uintptr had to be different (and it's also not an alias). It was only later that int and uint became machine-word-sized on all platforms and so made uintptr seem a bit redundant. However, this distinction is fortuitous for CHERI etc. support. Still, Go on CHERI with 128-bit uintptr might break some code, however such code is likely in violation of the unsafe pointer rules anyway: https://pkg.go.dev/unsafe#Pointer
Yet Rust is not Go and this solution is probably not the right one for Rust. As laid out in a link on a sibling comment, one possibility is to do away with pointer <=> integer conversions entirely, and use methods on pointers to access and mutate their addresses (which may be the only thing they represent on some platforms, but is just a part of their representation on others). The broader issue is really about evolving the language and ecosystem away from the mentality that "pointers are just integers with fancy sigil names".
I'd say, that even more than pointer sizes, the idea that a pointer is just a number really needs to die, and is in no way a forward looking decision expected of a modern language.
Pointers should at no point be converted into numbers and back as that trips up many assumptions (special runtimes, static/dynamic analysis tools, compiler optimizations).
Additionally, I would make it a priority that writing FFIs should be as easy as possible, and requires as little human deliberation as possible. Even if Rust is safe, its safety can only be assumed as long as the underlying external code upholds the invariants.
Which is a huge risk factor for Rust, especially in today's context of the Linux kernel. If I have an object created/handled by external native code, how do I make sure that it respects Rust's lifetime/aliasing rules?
What's the exact list of rules my C code must conform to?
Are there any static analysis/fuzzing tools that can verify that my code is indeed compliant?
C doesn't require convertibility to an integer and recognizes that pointers may have atypical representations. Casting to integer types has always been implementation defined. [u]intptr_t is optional specifically to allow such platforms to claim standard conformance.
> Which is a huge risk factor for Rust, especially in today's context of the Linux kernel. If I have an object created/handled by external native code, how do I make sure that it respects Rust's lifetime/aliasing rules?
Can you expand on this point? Like are you worried about whether the external code is going to free the memory out from under you? That is part of a guarantee, the compiler cannot guarantee what happens at runtime no matter what the author of a language wants, the CPU will do what it's told, it couldn't care about Rusts guarantees even if you built your code entirely with rust.
When you are interacting with the real world and real things you need to work with different assumptions, if you don't trust that the data will remain unmodified then copy it.
No matter how many abstractions you put on top of it there is still lighting in a rock messing with 1s and 0s.
> Is it something that can be fixed with editions? My guess is no, or at least not easily.
Assuming I'm reading these blog posts [0, 1] correctly, it seems that the size_of::<usize>() == size_of::<*mut u8>() assumption is changeable across editions.
Or at the very least, if that change (or a similarly workable one) isn't possible, both blog posts do a pretty good job of pointedly not saying so.
Personally, I like 3.1.2 from your link [0] best, which involves getting rid of pointer <=> integer casts entirely, and just adding methods to pointers, like addr and with_addr. This needs no new types and no new syntax, though it does make pointer arithmetic a little more cumbersome. However, it also makes it much clearer that pointers have provenance.
I think the answer to "can this be solved with editions" is more "kinda" rather than "no"; you can make hard breaks with a new edition, but since the old editions must still be supported and interoperable, the best you can do with those is issue warnings. Those warnings can then be upgraded to errors on a per-project basis with compiler flags and/or Cargo.toml options.
> Personally, I like 3.1.2 from your link [0] best, which involves getting rid of pointer <=> integer casts entirely, and just adding methods to pointers, like addr and with_addr. This needs no new types and no new syntax, though it does make pointer arithmetic a little more cumbersome. However, it also makes it much clearer that pointers have provenance.
Provenance-related work seems to be progressing at a decent pace, with some provenance-related APIs stabilized in Rust 1.84 [0, 1].
This feels antithetical to two of the main goals of editions.
One of those goals is that code which was written for an older edition will continue to work. You should never be forced to upgrade editions, especially if you have a large codebase which would require significant modifications.
The other goal is that editions are interoperable, i.e. that code written for one edition can rely on code written for a different edition. Editions are set on a per-crate basis, this seems to be the case for both rustc [1] and of course cargo.
As I see it, what you're saying would mean that code written for this new edition initially couldn't use most of the crates on crates.io as dependencies. This would then create pressure on those crates' authors to update their edition. And all of this would be kind of pointless fragmentation and angst, since most of those crates wouldn't be doing funny operations on pointers anyway. It might also have the knock-on effect of making new editions much more conservative, since nobody would want to go through that headache again, thus undermining another goal of editions.
If the part about how "most of those crates wouldn't be doing funny operations on pointers" can be verified automatically in a way that preserves safety guarantees when usize != uaddr/uptr, these crates can continue to build without transitioning to a newer edition. Otherwise, upgrading these crates is the right move. Other code targeting earlier editions of Rust would still build, they would simply need a compiler version update when depending on the newly-upgraded crates.
> while in standard C there is no way to express that.
In ISO Standard C(++) there's no SIMD.
But in practice C vector extensions are available in Clang and GCC which are very similar to Rust std::simd (can use normal arithmetic operations).
Unless you're talking about CPU specific intrinsics, which are available to in both languages (core::arch intrinsics vs. xmmintrin.h) in all big compilers.
In what architecture are those types different? Is there a good reason for it there architecturally, or is it just a toolchain idiosyncrasy in terms of how it's exposed (like LP64 vs. LLP64 etc.)?
CHERI has 64-bit object size but 128-bit pointers (because the pointer values also carry pointer provenance metadata in addition to an address). I know some of the pointer types on GPUs (e.g., texture pointers) also have wildly different sizes for the address size versus the pointer size. Far pointers on segmented i386 would be 16-bit object and index size but 32-bit address and pointer size.
There was one accelerator architecture we were working that discussed making the entire datapath be 32-bit (taking less space) and having a 32-bit index type with a 64-bit pointer size, but this was eventually rejected as too hard to get working.
I guess today, instead of 128bit pointers we have 64bit pointers and secret provenance data inside the cpu, at least on the most recent shipped iPhones and Macs.
In the end, I’m not sure that’s better, or maybe we should have had extra large pointers again (in that way back 32bit was so large we stuffed other stuff in there) like CHERI proposes (though I think it still has secret sidecar of data about the pointers).
Would love to Apple get closer to Cheri. They could make a big change as they are vertically integrated, though I think their Apple Silicon for Mac moment would have been the time.
You mean in safe rust? You can definitely do self-referential structs with unsafe and Pin to make a safe API. Heck every future generated by the compiler relies on this.
Sorry but what have I said wrong? The nature of code written in kernel development is such that using unsafe is inevitable. Low-level code with memory juggling and patterns that you usually don't find in application code.
And yes, I have had a look into the examples - maybe one or two years there was a significant patch submitted to the kernel and number of unsafe sections made me realize at that moment that Rust, in terms of kernel development, might not be what it is advertised for.
Right? Thank you for the example. Let's first start by saying the obvious - this is not an upstream driver but a fork and it is also considered by its author to be a PoC at best. You can see this acknowledged by its very web page, https://rust-for-linux.com/nvme-driver, by saying "The driver is not currently suitable for general use.". So, I am not sure what point did you try to make by giving something that is not even a production quality code?
Now let's move to the analysis of the code. The whole code, without crates, counts only 1500 LoC (?). Quite small but ok. Let's see the unsafe sections:
rnvme.rs - 8x unsafe sections, 1x SyncUnsafeCell used for NvmeRequest::cmd (why?)
nvme_mq/nvme_prp.rs - 1x unsafe section
nvme_queue.rs - 6x unsafe not sections but complete traits
nvme_mq.rs - 5x unsafe sections, 2x SyncUnsafeCell used, one for
IoQueueOperations::cmd second for AdminQueueOperations::cmd
In total, this is 23x unsafe sections/traits over 1500LoC, for a driver that is not even a production quality driver. I don't have time but I wonder how large this number would become if all crates this driver is using were pulled in into the analysis too.
The idea behind the safe/unsafe split is to provide safe abstractions over code that has to be unsafe.
The unsafe parts have to be written and verified manually very carefully, but once that's done, the compiler can ensure that all further uses of these abstractions are correct and won't cause UB.
Everything in Rust becomes "unsafe" at some lower level (every string has unsafe in its implementation, the compiler itself uses unsafe code), but as long as the lower-level unsafe is correct, the higher-level code gets safety guarantees.
This allows kernel maintainers to (carefully) create safe public APIs, which will be much safer to use by others.
C doesn't have such explicit split, and its abstraction powers are weaker, so it doesn't let maintainer create APIs that can't cause UB even if misused.
> I am not sure what point did you try to make by giving something that is not even a production quality code?
let's start by prefacing that 'production quality' C is 100% unsafe in Rust terms.
> Sorry, I am not buying that argument.
here's where we fundamentally disagree: you listed a couple dozen unsafe places in 1.5kLOC of code; let's be generous and say that's 10% - and you're trying to sell it as a bad thing, whereas I'm seeing the same numbers and think it's a great improvement over status quo ante.
> let's start by prefacing that 'production quality' C is 100% unsafe in Rust terms.
I don't know what one should even make from that statement.
> here's where we fundamentally disagree: you listed a couple dozen unsafe places in 1.5kLOC of code; let's be generous and say that's 10%
It's more than 10%, you didn't even bother to look at the code but still presented it, what in reality is a toy driver example, as something credible (?) to support your argument of me spreading FUD. Kinda silly.
Even if it was only that much (10%), the fact it is in the most crucial part of the code makes the argument around Rust safety moot. I am sure you heard of 90/10 rule.
The time will tell but I am not holding my breath. I think this is a bad thing for Linux kernel development.
> I don't know what one should even make from that statement.
it's just a fact. by definition of the Rust language unsafe Rust is approximately as safe as C (technically Rust is still safer than C in its unsafe blocks, but we can ignore that.)
> you didn't even bother to look at the code but still presented
of course I did, what I've seen were one-liner trait impls (the 'whole traits' from your own post) and sub-line expressions of unsafe access to bindings.
> technically Rust is still safer than C in its unsafe blocks
This is quite dubious in a practical sense, since Rust unsafe blocks must manually uphold the safety invariants that idiomatic Safe Rust relies on at all times, which includes, e.g. references pointing to valid and properly aligned data, as well as requirements on mutable references comparable to what the `restrict` qualifier (which is rarely used) involves in C. In practice, this is hard to do consistently, and may trigger unexpected UB.
Some of these safety invariants can be relaxed in simple ways (e.g. &Cell<T> being aliasable where &mut T isn't) but this isn't always idiomatic or free of boilerplate in Safe Rust.
It's great that the Google Android team has been tracking data to answer that question for years now and their conclusion is:
-------
The primary security concern regarding Rust generally centers on the approximately 4% of code written within unsafe{} blocks. This subset of Rust has fueled significant speculation, misconceptions, and even theories that unsafe Rust might be more buggy than C. Empirical evidence shows this to be quite wrong.
Our data indicates that even a more conservative assumption, that a line of unsafe Rust is as likely to have a bug as a line of C or C++, significantly overestimates the risk of unsafe Rust. We don’t know for sure why this is the case, but there are likely several contributing factors:
unsafe{} doesn't actually disable all or even most of Rust’s safety checks (a common misconception).
The practice of encapsulation enables local reasoning about safety invariants.
The additional scrutiny that unsafe{} blocks receive.
> The practice of encapsulation enables local reasoning about safety invariants.
> The additional scrutiny that unsafe{} blocks receive.
None of this supports an argument that "unsafe Rust is safer than C". It's just saying that with enough scrutiny on those unsafe blocks, the potential bugs will be found and addressed as part of development. That's a rather different claim.
It does, if you read the report and run a little (implied) math.
The report says that their historical data gives them an estimate of 1000 Memory Safety issues per Million Lines of Code for C/C++.
The same team currently has 5 Million lines of Rust code, of which 4% are unsafe (200 000). Assuming that unsafe Rust is on par with C/C++, this gives us an expected value of about 200 memory safety issues in the unsafe code. They have one. Either they have 199 hidden and undetected memory safety issues, or the conclusion is that even unsafe Rust is orders of magnitude better than C/C++ when it comes to memory safety.
I trust them to track these numbers diligently. This is a seasoned team building foundational low level software. We can safely assume that the Android team is better than the average C/C++ programmer (and likely also than the average Rust programmer), so the numbers should generalize fairly well.
Part of the benefits of Rust is indeed that it allows local reasoning about crucial parts of the code. This does allow for higher scrutiny which will find more bugs, but that's a result of the language design. unsafe {} was designed with that im mind - this is not a random emergent property.
They say "With roughly 5 million lines of Rust in the Android platform and one potential memory safety vulnerability found (and fixed pre-release), our estimated vulnerability density for Rust is 0.2 vuln per 1 million lines (MLOC).".
Do you honestly believe that there is 1 vulnerability per 5 MLoC?
1 memory safety vulnerability, that's a pretty important distinction.
Yes, I believe that at least the order of magnitude is correct because 4 800 000 of those lines are guaranteed to not have any by virtue of the compiler enforcing memory safety.
So it's 1 per 200 000, which is 1-2 orders of magnitude worse, but still pretty darn good. Given that not all unsafe code actually has potential for memory safety issues and that the compiler still will enforce a pretty wide set of rules, I consider this to be achievable.
This is clearly a competent team that's writing important and challenging low-level software. They published the numbers voluntarily and are staking their reputation on these reports. From personal observation of the Rust projects we work on, the results track with the trend.
There's no reason for me to disbelieve the numbers put forward in the report.
You accidentally put the finger on the key point, emphasis mine.
When you have a memory-unsafe language, the complexity of the whole codebase impact your ability to uphold memory-related invariants.
But unsafe block are, by definition, limited in scope and assuming you design your codebase properly, they shouldn't interact with other unsafe blocks in a different module. So the complexity related to one unsafe block is in fact contained to his own module, and doesn't spread outside. And that makes everything much more tractable since you never have to reason about the whole codebase, but only about a limited scope everytime.
No, this is just an example of confirmation bias. You're given a totally unrealistic figure of 1 vuln per 200K/5M LoC and now you're hypothesizing why that could be so. Google, for anyone unbiased, lost the credibility when they put this figure into the report. I wonder what was their incentive for doing so.
> But unsafe block are, by definition, limited in scope and assuming you design your codebase properly, they shouldn't interact with other unsafe blocks in a different module. So the complexity related to one unsafe block is in fact contained to his own module, and doesn't spread outside. And that makes everything much more tractable since you never have to reason about the whole codebase, but only about a limited scope everytime.
For anyone who has written low-level code with substantial complexity knows that this is just a wishful thinking. In such code, abstractions fall-apart and "So the complexity related to one unsafe block is in fact contained to his own module, and doesn't spread outside" is just wrong as I explained in my other comment here - UB taking place in unsafe section will transcend into the rest of the "safe" code - UB is not "caught" or put into the quarantine with some imaginative safety net at the boundary between the safe and unsafe sections.
Let's take a simple example to illustrate how unsafe {} cuts down the review effort for many operations. Take a static mutable global variable (a global counter for example). Reading a static is safe, mutating it (increasing the counter) is not - it requires an unsafe {} block.
If you need to check which places mutate this global static you only need to check the unsafe parts of the code - you know that no other part of your code could mutate it, the compiler won't let you. If you have a bug that is related to mutating this static, then it might manifest anywhere in your code. But you know for certain that the root cause must be in one of your unsafe blocks - even if you don't know which one.
Good programming practice will cut down that effort even more by dictating that unsafe access should be grouped in modules. For example when binding to a C module (unsafe) you'd usally generate an unsafe wrapper with bindgen and then write a safe wrapper on top of that. Any access that tries to go around the safe wrapper would be frowned upon and likely fail review.
And again, the compiler will help you there: Any access that tries to bypass the safe api would need to be unsafe {} again and automatically receive extra scrutiny in a review, making it less likely to slip through.
Compare that to a C codebase where anything goes. A static might be mutated anywhere in your codebase, even through a pointer to it - meaning you can't even reliably grep for it. It may slip through review unnoticed because no attention is drawn to it and cause bugs that are hard to trace and reason about.
If you're writing embedded code, similar considerations apply - access to registers etc. require unsafe {}. But because access is unsafe {}, it's usually gated behind a safe api that is the boundary of the low-level code and the higher buisness logic. Unsurprisingly, these are critical parts of the code - hence they receive extra scrutiny and in our project, we allocate substantial review capacity on those. And the compiler will enforce that no safe code can circumvent the access layer.
The number you're tagging as unrealistic figure is the result of dedicated and careful design of language and compiler features to achieve exactly this outcome. It's not a random fluke, very clever people did sit down and thought about how to achieve this.
> You're given a totally unrealistic figure of 1 vuln per 200K/5M LoC and now you're hypothesizing why that could be so.
You are the one claiming it's unrealistic. And you gave zero argument why besides “the codebase is complex”, which I refuted. See the definition of complexity:
> The term is generally used to characterize something with many parts where those parts interact with each other in multiple ways, culminating in a higher order of emergence greater than the sum of its parts
Each unsafe block may be “difficult” in itself, but the resulting system isn't “complex” because you don't have this compounding effect.
> I wonder what was their incentive for doing so.
And obviously it must be malice…
> For anyone who has written low-level code with substantial complexity knows that this is just a wishful thinking. In such code, abstractions fall-apart and "So the complexity related to one unsafe block is in fact contained to his own module, and doesn't spread outside" is just wrong as I explained in my other comment here - UB taking place in unsafe section will transcend into the rest of the "safe" code - UB is not "caught" or put into the quarantine with some imaginative safety net at the boundary between the safe and unsafe sections.
I think you don't understand the problem as well as you think you do. Of course if the UB happens then all bets are off! Its consequences won't be limited to a part of the code, by definition. And nobody said otherwise.
But for the UB to happen, there must be some violation of an memory invariant (the most common would be using a value after free, freeing twice, accessible the same memory from multiple threads without synchronization or, and this is specific to Rust, violating reference aliasing rules).
To avoid violating these invariants, the programmer must have a mental model of the ownership over all the system on which these invariants apply. For C or C++, it means having a mental model of all the code base, because the invariants related to one piece of code can be violated from everywhere.
In Rust this is different, you're not going to have raw pointers to one piece of data being used in multiple parts of the code (well, if you really want, nobody stops you, but I'm confident the Android team didn't). And as such, you'll have to think about the invariants only at the scale of one module. Building an accurate mental model of a 350-line module is much more tractable for a human than doing the same for an entire codebase, and it's not even close.
That's the other interesting observation you can draw from that report. The numbers contained in the first parts about review times, rollback rates, etc. are broken down by change size. And the gap widens for larger changes. This indicates that Rusts language features support reasoning about complex changesets.
It's not obviously clear to me which features are the relevant ones, but my general observation is that lifetimes, unsafe blocks, the borrow checker allow people to reason about code in smaller chunks. For example knowing that there's only one place where a variable may be mutated supports understanding that at the same time, no other code location may change it.
The practice of encapsulation enables local reasoning about safety invariants.
which is not fully correct. Undefined behavior in unsafe blocks can and will leak into the safe Rust code so there is nothing there about the "local reasoning" or "encapsulation" or "safety invariants".
This whole blog always read to me as too much like a marketing material disguised with some data so that it is not so obvious. IMHO
> which is not fully correct. Undefined behavior in unsafe blocks can and will leak into the safe Rust code so there is nothing there about the "local reasoning" or "encapsulation" or "safety invariants".
Strictly speaking, that encapsulation enables local reasoning about safety invariants does not necessarily imply that encapsulation guarantees local reasoning about safety invariants. It's always possible to write something unadvisable, and no language is capable of preventing that.
That being said, I think you might be missing the point to some extent. The idea behind the sentence is not to say that the consequences of a mistake will not be felt elsewhere. The idea is that when reasoning about whether you're upholding invariants and/or investivating something that went wrong, the amount of code you need to look at is bounded such that you can ignore everything outside those bounds; i.e., you can look at some set of code in complete isolation. In the most conservative/general case that boundary would be the module boundary, but it's not uncommon to be able to shrink those boundaries to the function body, or potentially even further.
This general concept here isn't really new. Rust just applied it in a relatively new context.
Yes, but my point is when things blow up how exactly do you know which unsafe block you should look into? From their statement it appears as if there's such a simple correlation between "here's your segfault" and "here's your unsafe block that caused it", and which I believe there isn't, and which is why I said there's no encapsulation, local reasoning etc.
> Yes, but my point is when things blow up how exactly do you know which unsafe block you should look into?
In the most general case, you don't. But again, I think that that rather misses the point the statement was trying to get at.
Perhaps a more useful framing for you would be that in the most general case the encapsulation and local reasoning here is between modules that use unsafe and everything else. In some (many? most?) cases you can further bound how much code you need to look at if/when something goes wrong since not all code in unsafe modules/functions/blocks depend on each other, but in any case the point is that you only need to inspect a subset of code when reasoning about safety invariants and/or debugging a crash.
> From their statement it appears as if there's such a simple correlation between "here's your segfault" and "here's your unsafe block that caused it",
> in the most general case the encapsulation and local reasoning here is between modules that use unsafe and everything else
This would be the same narrative as in, let's say, C++. Wrap the difficult and low-level memory juggling stuff into "modules", harden the API, return the references and/or smart-pointers, and then just deal with the rest of the code with ease, right? Theoretically possible but practically impossible.
First reason is that abstractions get really leaky, and they especially get really leaky in the code that demands the upmost performance. Anyone who implemented their own domain/workload specific hash-map or mutex or anything similarly foundational will understand this sentiment. Anyway, if we just have a look into the NVMe driver above, there're no "unsafe modules".
Second, and as I already argued, UB in the module library transcends into the rest of your code so I fail to understand how is it so that the dozens of unsafe sections make the reasoning or debugging any more simpler when reasoning is actually not a function of number of unsafe sections but it is the function of interactions between different parts of the code that end up touching the memory in the unsafe block in a way that it was not anticipated. This is almost always the case when dealing with undefined behavior.
> I don't get that sense from the statement at all.
It is a bit exaggerated example of mine but I do - their framing suggests ~exactly that and which is simply not true.
> This would be the same narrative as in, let's say, C++. Wrap the difficult and low-level memory juggling stuff into "modules", harden the API, return the references and/or smart-pointers, and then just deal with the rest of the code with ease, right? Theoretically possible but practically impossible.
The difference, of course, is in the amount of automated help/enforcement provided that makes it harder/impossible to misuse said API. Just like C++ provides new functionality compared to C that makes it hard-to-impossible to misuse APIs in certain ways (RAII, stronger type system, etc.), and how C does the same compared to assembly (providing structured control flow constructs, abstracting away low-level details like calling convention/register management, etc.), Rust provides new functionality that previous widespread languages didn't. It's those additional capabilities that make previously difficult things practical.
> so I fail to understand how is it so that the dozens of unsafe sections make the reasoning or debugging any more simpler when reasoning is actually not a function of number of unsafe sections but it is the function of interactions between different parts of the code that end up touching the memory in the unsafe block in a way that it was not anticipated.
...Because the way encapsulation works in practice is that only a subset of code can "touch[] the memory in the unsafe block in a way that it was not anticipated" in the first place? That's kind of the point of encapsulation!
(I say "in practice" because Rust doesn't and can't stop you from writing unsafe APIs, but that's going to be true of any language due to Rice's Theorem, the halting problem, etc.)
As a simple example, say you have a program with one singular unsafe block encapsulated in one single function which is intended to provide a safe API. If/when UB happens the effects can be felt anywhere, but you know the bug is within the encapsulation boundary - i.e., in the body of the function that wraps the unsafe block, even if the bug is not in the unsafe block itself (well, either that or a compiler bug but that's almost always not going to be the culprit). That certainly seems to me like it'd be easier to debug than having to reason about the entire codebase.
This continues to scale up to multiple functions which provide either a combined or independent API to internal unsafe functionality, whole modules, etc. Sure, debugging might be more difficult than the single-function case due to the additional possibilities, but the fact remains that for most (all?) codebases the amount of code responsible for/causing UB will reside behind said boundary and is going to be a proper subset of the all the code in the project.
And if you take this approach to the extreme, you end up with formal verification programs/theorem provers, which isolate all their "unsafe" code to a relatively small/contained trusted kernel.Even there, UB in the trusted kernel can affect all parts of compiled programs, but the point is that if/when something goes wrong you know the issue is going to be in the trusted kernel, even if you don't necessarily know precisely where.
> It is a bit exaggerated example of mine but I do - their framing suggests ~exactly that and which is simply not true.
I do agree that that the claim in that particular interpretation of that statement is wrong (and Rust has never offered such a correlation in the first place), but it's kind of hard to discuss beyond that if I don't interpret that sentence the same way you do :/
It actually does support it. Human attention is a finite resource. You can spend a little bit if attention in every line to scrutinize safety or you can spend a lot of time scrutinizing the places where you can't mechanically guarantee safety.
It's safer because it spends the human attention resource more wisely.
No, I am not saying keep the status quo. I am simply challenging the idea that kernel will enjoy benefits that is supposed to be provided by Rust.
Distribution of bugs across the whole codebase is not following the normal distribution but multimodal. Now, imagine where the highest concentration of bugs will be. And how many bugs there will be elsewhere. Easy to guess.
What am I exactly doing again? I am providing my reasoning, sorry if that itches you the wrong way. I guess you don't have to agree but let me express my view, ok? My view is not extremist or polarized as you see. I see the benefit of Rust but I say the benefit is not what Internet cargo-cult programming suggests. There's always a price to be paid, and in case of kernel development I think it outweighs the positive sides.
If I spend 90% of time debugging freaking difficult to debug issues, and Rust solves the other 10% for me, then I don't see it as a good bargain. I need to learn a completely new language, surround myself with a team which is also not hesitant to learn it, and all that under assumption that it won't make some other aspects of development worse. And for surely it will.
Every system under the Sun has a C compiler. This isn't remotely true for Rust. Rust is more modern than C, but has it's own issues, among others very slow compilation times. My guess is that C will be around long after people will have moved on from Rust to another newfangled alternative.
There is a set of languages which are essentially required to be available on any viable system. At present, these are probably C, C++, Perl, Python, Java, and Bash (with a degree of asterisks on the last two). Rust I don't think has made it through that door yet, but on current trends, it's at the threshold and will almost certainly step through. Leaving this set of mandatory languages is difficult (I think Fortran, and BASIC-with-an-asterisk, are the only languages to really have done so), and Perl is the only one I would risk money on departing in my lifetime.
I do firmly expect that we're less than a decade out from seeing some reference algorithm be implemented in Rust rather than C, probably a cryptographic algorithm or a media codec. Although you might argue that the egg library for e-graphs already qualifies.
We're already at the point where in order to have a "decent" desktop software experience you _need_ Rust too. For instance, Rust doesn't support some niche architectures because LLVM doesn't support them (those architectures are now exceedingly rare) and this means no Firefox for instance.
> There is a set of languages which are essentially required to be available on any viable system. At present, these are probably C, C++, Perl, Python, Java, and Bash
Java, really? I don’t think Java has been essential for a long time.
Even Perl... It's not in POSIX (I'm fairly sure) and I can't imagine there is some critical utility written in Perl that can't be rewritten in Python or something else (and probably already has been).
As much as I like Java, Java is also not critical for OS utilities. Bash shouldn't be, per se, but a lot of scripts are actually Bash scripts, not POSIX shell scripts and there usually isn't much appetite for rewriting them.
I am yet to find one without it, unless you include Windows, consoles, or table/phone OSes, embedded RTOS, which aren't anyway proper UNIX derivatives.
I personally feel the Zig is a much better fit to the kernel. It's C interoperability is far better than Rust's, it has a lower barrier to entry for existing C devs and it doesn't have the constraints the Rust does. All whilst still bringing a lot of the advantages.
...to the extent I could see it pushing Rust out of the kernel in the long run. Rust feels like a sledgehammer to me where the kernel is concerned.
It's problem right now is that it's not stable enough. Language changes still happen, so it's the wrong time to try.
From a safety perspective there isn't a huge benefit to choosing Zig over C with the caveat, as others have pointed out, that you need to enable more tooling in C to get to a comparable level. You should be using -Wall and -fsanitize=address among others in your debug builds.
You do get some creature comforts like slices (fat pointers) and defer (goto replacement). But you also get forced to write a lot of explicit conversions (I personally think this is a good thing).
The C interop is good but the compiler is doing a lot of work under the hood for you to make it happen. And if you export Zig code to C... well you're restricted by the ABI so you end up writing C-in-Zig which you may as well be writing C.
It might be an easier fit than Rust in terms of ergonomics for C developers, no doubt there.
But I think long-term things like the borrow checker could still prove useful for kernel code. Currently you have to specify invariants like that in a separate language from C, if at all, and it's difficult to verify. Bringing that into a language whose compiler can check it for you is very powerful. I wouldn't discount it.
> I kind of doubt the Linux maintainers would want to introduce a third language to the codebase.
That was where my argument was supposed to go. Especially a third language whose benefits over C are close enough to Rust's benefits over C.
I can picture an alternate universe where we'd have C and Zig in the kernel, then it would be really hard to argue for Rust inclusion.
(However, to be fair, the Linux kernel has more than C and Rust, depending on how you count, there are quite a few more languages used in various roles.)
IMHO Zig doesn't bring enough value of its own to be worth bearing the cost of another language in the kernel.
Rust is different because it both:
- significantly improve the security of the kernel by removing the nastiest class of security vulnerabilities.
- And reduce cognitive burden for contributors by allowing to encode in thr typesystem the invariants that must be upheld.
That doesn't mean Zig is a bad language for a particular project, just that it's not worth adding to an already massive project like the Linux kernel. (Especially a project that already have two languages, C and now Rust).
Pardon my ignorance but I find the claim "removing the nastiest cla ss of security vulnerabilities" to be a bold claim. Is there ZERO use of "unsafe" rust in kernel code??
Aside from the minimal use of unsafe being heavily audited and the only entry point for those vulnerabilities, it allows for expressing kernel rules explicitly and structurally whereas at best there was a code comment somewhere on how to use the API correctly. This was true because there was discussion precisely about how to implement Rust wrappers for certain APIs because it was ambiguous how those APIs were intended to work.
So aside from being like 1-5% unsafe code vs 100% unsafe for C, it’s also more difficult to misuse existing abstractions than it was in the kernel (not to mention that in addition to memory safety you also get all sorts of thread safety protections).
In essence it’s about an order of magnitude fewer defects of the kind that are particularly exploitable (based on research in other projects like Android)
"Unsafe" rust still upholds more guarantees than C code. The rust compiler still enforces the borrow checker (including aliasing rules) and type system.
There are devices that do not have access to memory, and you can write a safe description of such a device's registers. The only thing that is inherently unsafe is building DMA descriptors.
Not zero, but Rust-based kernels (see redox, hubris, asterinas, or blog_os) have demonstrated that you only need a small fraction of unsafe code to make a kernel (3-10%) and it's also the least likely places to make a memory-related error in a C-based kernel in the first place (you're more likely to make a memory-related error when working on the implementation of an otherwise challenging algorithm that has nothing to do with memory management itself, than you are when you are explicitly focused on the memory-management part).
So while there could definitely be an exploitable memory bug in the unsafe part of the kernel, expect those to be at least two orders of magnitude less frequent than with C (as an anecdotal evidence, the Android team found memory defects to be between 3 and 4 orders of magnitude less in practice over the past few years).
It removes a class of security vulnerabilities, modulo any unsound unsafe (in compiler, std/core and added dependency).
In practice you see several orders of magnitude fewer segfaults (like in Google Android CVE). You can compare Deno and Bun issue trackers for segfaults to see it in action.
As mentioned a billion times, seatbelts don't prevent death, but they do reduce the likelihood of dying in a traffic accident. Unsafe isn't a magic bullet, but it's a decent caliber round.
> Our historical data for C and C++ shows a density of closer to 1,000 memory safety vulnerabilities per MLOC. Our Rust code is currently tracking at a density orders of magnitude lower: a more than 1000x reduction.
Zig as a language is not worth, but as a build system it's amazing. I wouldn't be surprised if Zig gets in just because of the much better build system than C ever had (you can cross compile not only across OS, but also across architecture and C stlib versions, including musl). And with that comes the testing system and seamless interop with C, which make it really easy to start writing some auxiliary code in Zig... and eventually it may just be accepted for any development.
I agree with you that it's much more interesting than the language, but I don't think it matters for a project like the Kernel that already had its build system sorted out. (Especially since no matter how nice and convenient Zig makes cross-compilation if you start a project from scratch, even in Rust thanks to cargo-zigbuild, it would require a lot of efforts to migrate the Linux build system to Zig, only to realize it doesn't support all the needs of the kernel at the start).
I can’t think of many real world production systems which don’t have a rust target. Also I’m hopeful the GCC backend for rustc makes some progress and can become an option for the more esoteric ones
There aren't really any "systems programming" platforms anywhere near production that doesn't have a workable rust target.
It's "embedded programming" where you often start to run into weird platforms (or sub-platforms) that only have a c compiler, or the rust compiler that does exist is somewhat borderline. We are sometimes talking about devices which don't even have a gcc port (or the port is based on a very old version of gcc).
Which is a shame, because IMO, rust actually excels as an embedded programming language.
Linux is a bit marginal, as it crosses the boundary and is often used as a kernel for embedded devices (especially ones that need to do networking). The 68k people have been hit quite hard by this, linux on 68k is still a semi-common usecase, and while there is a prototype rust back end, it's still not production ready.
There’s also I believe an effort to target C as the mid level although I don’t know the state / how well it’ll work in an embedded space anyway where performance really matters and these compilers have super old optimizers that haven’t been updated in 3 decades.
It's mostly embedded / microcontroller stuff. Things that you would use something like SDCC or a vendor toolchain for. Things like the 8051, stm8, PIC or oddball things like the 4 cent Padauk micros everyone was raving about a few years ago. 8051 especially still seems to come up from time to time in things like the ch554 usb controller, or some NRF 2.4ghz wireless chips.
Those don’t really support C in any real stretch, talking about general experience with microcontrollers and closed vendor toolchains; it’s a frozen dialect of C from decades ago which isn’t what people think of when they say C (usually people mean at least the 26 year old C99 standard but these often at best support C89 or even come with their own limitations)
It’s still C though, and rust is not an option. What else would you call it? Lots of c libraries for embedded target C89 syntax for exactly these reasons. Also for what it’s worth, SDCC seems to support very modern versions of C (up to C23), so I also don’t think that critique is very valid for the 8051 or stm8. I would argue that c was built with targets like this in mind and it is where many of its quirks that seem so anachronistic today come from (for example int being different sizes on different targets)
I’m sure if Rust becomes more popular in the game dev community, game consoles support will be a solved problem since these consoles are generally just running stock PC architectures with a normal OS and there’s probably almost nothing wrong with the stock toolchain in terms of generating working binaries: PlayStation and Switch are FreeBSD (x86 vs ARM respectively) and Xbox is x86 Windows, all of which are supported platforms.
Being supported on a games console means that you can produce binaries that are able to go through the whole approval process, and there is day 1 support for anything that is provided by the console vendor.
Otherwise is yak shaving instead of working on the actual game code.
Some people like to do that, that is how new languages like Rust get adoption, however they are usually not the majority, hence why mainstream adoption without backing from killer projects or companies support is so hard, and very few make it.
Also you will seldom see anyone throw away the confort of Unreal, Unity or even Godot tooling, to use Rust instead, unless they are more focused on proving the point the game can be made in Rust, than the actual game design experience.
Good luck shipping arbitrary binaries to this target. The most productive way for an indie to ship to Nintendo in 2025 is to create the game Unity and build via the special Nintendo version of the toolchain.
How long do we think it would take to fully penetrate all of these pipeline stages with rust? Particularly Nintendo, who famously adopts the latest technology trends on day 1. Do we think it's even worthwhile to create, locate, awaken and then fight this dragon? C# with incremental GC seems to be more than sufficient for a vast majority of titles today.
GNU/Linux isn't the only system out there, POSIX last update was in 2024 (when C requirement got upgraded to C17), and yes some parts of it are outdated in modern platforms, especially Apple, Google, Microsoft OSes and Cloud infrastructure.
Still, who supports POSIX is expected to have a C compiler available if applying for the OpenGroup stamp.
> GNU/Linux isn't the only system out there, [...]
Yes? I'm especially keen on the project to make just 'Linux' without any Gnu components. Jokes aside, yes, other operating systems exist.
> [...] and yes some parts of it are outdated in modern platforms, especially Apple, Google, Microsoft OSes and Cloud infrastructure.
Well, also on modern hardware in general. POSIX's idea about storage abstractions don't gel well with modern hardware. Well, not just modern hardware: atime has been cast aside since time immemorial. But lots of the other ideas are also dropping by the wayside.
In practice, we get something like 'POSIX-y' or 'close-enough-to-POSIX', which doesn't get a stamp from OpenGroup. I think Linux largely falls in that boat.
But regardless of that distinction, a C compiler is very much part of the informal 'close-enough-to-POSIX' class.
It's a bit of a shame that Unix has retarded operating system research for so long. Fortunately, the proliferation of virtual machines and hypervisors have re-invigorated the field. Just pretend that your hypervisor is your nano-kernel and your VMs are your processes. No need to run a full on Linux kernel in your VM, you can just have enough code running for your webserver or database etc, no general kernel needed. See eg https://mirage.io/
That isn't always the case. Slow compilations are usually because of procedural macros and/or heavy use of generics. And even then compile times are often comparable to languages like typescript and scala.
Compared to C, rust compiles much slower. This might not matter on performant systems, but when resources are constrained you definitely notice it.
And if the whole world is rewritten in Rust, this will have a non-significant impact on the total build time of a bunch of projects.
It's been mentioned obliquely, but what's considered "part of the compilation process" is different between Rust and C.
Most of the examples of "this is what makes Rust compilation slow" are code generation related; for example, a Rust proc_macro making compilation slow would be equivalent to C building a code generator (for schemas? IDLs?), running it, then compiling the output along with its user.
Rust compilation is also almost instant, if you have a relatively small project.
It's only when you get very large projects that compilation time becomes a problem.
If typescript compilation speed wasn't a problem, then why would Microsoft put resources into rewriting the tyescript compiler in Go to make it faster[1]?
rustc can use a few different backends. By my understanding, the LLVM backend is fully supported, the Cranelift backend is either fully supported or nearly so, and there's a GCC backend in the works. In addition, there's a separate project to create an independent Rust frontend as part of GCC.
Even then, there are still some systems that will support C but won't support Rust any time soon. Systems with old compilers/compiler forks, systems with unusual data types which violate Rust's assumptions (like 8 bit bytes IIRC)
Many organizations and environments will not switch themselves to LLVM to hamfist compiled Rust code. Nor is the fact of LLVM supporting something in principle means that it's installed on the relevant OS distribution.
Using LLVM somewhere in the build doesn't require that you compile everything with LLVM. It generates object files, just like GCC, and you can link together object files compiled with each compiler, as long as they don't use compiler-specific runtime libraries (like the C++ standard library, or a polyfill compiler-rt library).
If you're developing, you generally have control over the development environment (+/-) and you can install things. Plus that already reduces the audience: set of people with oddball hardware (as someone here put it) intersected with the set of people with locked down development environments.
Let alone the fact that conceptually people with locked down environments are precisely those would really want the extra safety offered by Rust.
I know that real life is messy but if we don't keep pressing, nothing improves.
> If you're developing, you generally have control over the development environment
If you're developing something individually, then sure, you have a lot of control. When you're developing as part of an organization or a company, you typically don't. And if there's non-COTS hardware involved, you are even more likely not to have control.
One problem of Rust vs C, I rarely see mentioned is that Rust code is hard to work with from other languages. If someone writes a popular C library, you can relatively easily use it in your Java, D, Nim, Rust, Go, Python, Julia, C#, Ruby, ... program.
If someone writes a popular Rust library, it's only going to be useful to Rust projects.
With Rust I don’t even know if it’s possible to make borrow checking work across a language boundary. And Rust doesn't have a stable ABI, so even if you make a DLL, it’ll only work if compiled with the exact same compiler version. *sigh*
The reason you rarely hear that mentioned is because basically every programming language is hard to work with from other languages. The usual solution is to "just" expose a C API/ABI.
Of course, C API/ABIs aren't able to make full use of Rust features, but again, that also applies to basically every other language.
I don't think they should be defined in terms of any programming language. They should be defined in terms of the architecture - bits, bytes, words and instructions, along with semantics of use. Like we do with networking protocols and file formats.
> Every system under the Sun has a C compiler... My guess is that C will be around long after people will have moved on from Rust to another newfangled alternative.
This is still the worst possible argument for C. If C persists in places no one uses, then who cares?
I'm curious as to which other metric you'd use to define successful? If it actual usage, C still wins. Number of new lines pushed into production each year, or new project started, C is still high up the list, would be my guess.
Languages like Rust a probably more successful in terms of age vs. adoption speed. There's just a good number of platforms which aren't even supported, and where you have no other choice than C. Rust can't target most platforms, and it compiles on even less. Unless Linux want's to drop support for a good number of platforms, Rust adoption can only go so far.
> I'm curious as to which other metric you'd use to define successful? If it actual usage, C still wins.
Wait, wait, you can use C everywhere, but if absolute lines of code is the metric, people seem to move away from C as quickly as possible (read: don't use C) to higher level languages that clearly aren't as portable (Ruby, Python, Java, C#, C++, etc.)?
> Rust can't target most platforms, and it compiles on even less. Unless Linux want's to drop support for a good number of platforms, Rust adoption can only go so far.
Re: embedded, this is just terribly short sighted. Right now, yes, C obviously has the broadest possible support. But non-experimental Linux support starts today. GCC is still working on Rust support and rust_codegen_gcc is making in roads. And one can still build new LLVM targets. I'd be interested to hear what non-legacy platforms actually aren't supported right now.
But the real issue is that C has no adoption curve. It only has ground to lose. Meanwhile Rust is, relatively, a delight to use, and offers important benefits. As I said elsewhere: "Unless I am wrong, there is still lots of COBOL we wish wasn't COBOL? And that reality doesn't sound like a celebration of COBOL?" The future looks bright for Rust, if you believe as I do, we are beginning to demand much more from our embedded parts.
Now, will Rust ever dominate embedded? Will it be used absolutely everywhere? Perhaps not. Does that matter? Not AFAIAC. Winning by my lights is -- using this nice thing rather than this not so nice thing. It's doing interesting things where one couldn't previously. It's a few more delighted developers.
I'd much rather people were delighted/excited about a tech/a language/a business/a vibrant community, than, whatever it is, simply persisted. Because "simply persisted", like your comment above, is only a measure of where we are right now.
Another way to think of this is -- Perl is also everywhere. Shipped with every Linux distro and MacOS, probably to be found somewhere deep inside Windows too. Now, is Perl, right now, a healthy community or does it simply persist? Is its persistence a source of happiness or dread?
Why do you think people are delighted/excited about Rust? Some people are, but I’m sure plenty of people are about C and Perl is well. Some people isn’t most people and certainly not all people.
> Some people isn’t most people and certainly not all people.
I never said Rust was the universal language, that is pleasing to all people. I was very careful to frame Rust's success in existential, not absolute or universal, terms:
>> Winning by my lights is -- using this nice thing rather than this not so nice thing. It's doing interesting things where one couldn't previously. It's a few more delighted developers.
I am not saying these technologies (C and Perl) should go away. I am saying -- I am very pleased there is no longer a monoculture. For me, winning is having alternatives to C in embedded and kernel development, and Perl for scripting, especially as these communities, right now, seem less vibrant, and less adaptable to change.
>> Yes, you can use it everywhere. Is that what you consider a success?
> ... yes?
Then perhaps you and I define success differently? As I've said in other comments above, C persisting or really standing still is not what I would think of as a winning and vibrant community. And the moving embedded and kernel development from what was previously a monoculture to something more diverse could be a big win for developers. My hope is that competition from Rust makes using C better/easier/more productive, but I have my doubts as to whether it will move C to make changes.
Sometimes it's nice to know that something will run and compile reliably far into the future. That's a nice thing to have, and wide support for the language and its relatively unchanging nature make it reliable.
> it always has been and always will be available everywhere
"Always has been" is pushing it. Half of C's history is written with outdated, proprietary compilers that died alongside their architecture. It's easy to take modern tech like LLVM for granted.
This might actually be a solved problem soonish, LLMs are unreasonably effective at writing compilers and C is designed to be easy to write a compiler for, which also helps. I don’t know if anyone tried, but there’s been related work posted here on HN recently: a revived Java compiler and the N64 decompilation project. Mashed together you can almost expect to be able to generate C compilers for obscure architectures on demand given just some docs and binary firmware dumps.
Which is neat! But if you care about performance, you're probably using Clang to compile for your esoteric architecture. And if your esoarch already supports LLVM... might as well write Rust instead.
You almost certainly have a bunch of devices containing a microcontroller that runs an architecture not targeted by LLVM. The embedded space is still incredibly fragmented.
That said, only a handful of those architectures are actually so weird that they would be hard to write a LLVM backend for. I understand why the project hasn’t established a stable backend plugin API, but it would help support these ancillary architectures that nobody wants to have to actively maintain as part of the LLVM project. Right now, you usually need to use a fork of the whole LLVM project when using experimental backends.
> You almost certainly have a bunch of devices containing a microcontroller that runs an architecture not targeted by LLVM.
This is exactly what I'm saying. Do you think HW drives SW or the other way around? When Rust is in the Linux kernel, my guess is it will be very hard to find new HW worth using, which doesn't have some Rust support.
In embedded, HW drives SW much more than the other way around. Most microcontrollers are not capable of running a Linux kernel as it is, even with NoMMU. The ones that are capable of this are overwhelmingly ARM or RISC-V these days. There’s not a long list of architectures supported by the modern Linux kernel but not LLVM/Rust.
Almost none of them, but that doesn’t make them “places no one uses”, considering everyone who can see these words is looking at a device with a bunch of C firmware running on microcontrollers all over the place.
You're putting words in my mouth there - that's not my point.
My point is that if they're not running linux, and the ones that are aren't running a modern kernel, then we shouldn't hold back development of modern platforms for ones that refuse to keep up.
There are certain styles of programming and data structure implementations that end up requiring you to fight Rust at almost every step. Things like intrusive data structures, pointer manipulation and so on. Famously there is an entire book online on how to write a performant linked list in idiomatic Rust - something that is considered straightforward in C.
For these cases you could always use Zig instead of C
Sure. Now import a useful performant B+tree in C from a reusable library, while enforcing type safety for your keys and values.
Lots of things C chose to use intrusive pointers and custom data structures for, you would program very differently in a different language.
I'm an old C neckbeard and I find Rust a great experience. Some of the arguments against it sound like people are complaining about how hard it is to run pushing a bicycle.
Given Zig's approach to safety, you can get the same in C with static and runtime analysis tools, that many devs keep ignoring.
Already setting the proper defaults on a Makefile would get many people half way there, without changing to language yet to be 1.0, and no use-after-free story.
it is not straightforward in rust because the linked list is inherently tricky to implement correctly. Rust makes that very apparent (and, yeah, a bit too apparent).
I know, a linked list is not exactly super complex and rust makes that a bit tough. But the realisation one must have is this: building a linked list will break some key assumptions about memory safety, so trying to force that into rust is just not gonna make it.
Problem is I guess that for several of us, we have forgotten about memory safety and it's a bit painful to have that remembered to us by a compiler :-)
Can you elaborate, what key assumptions about memory safety linked lists break? Sure, double linked lists may have non-trivial ownership, but that doesn't compromise safety.
Rust wants all memory to be modeled as an ownership tree: the same bit of memory can't be owned by more than one data structure. A doubly linked list breaks that requirement so it can't be modeled in safe Rust directly. The options are using unsafe, or using one of the pointer wrapper types that have runtime checks that ensure correct behavior and own the underlying memory as far Rust is concerned.
Right. So it is not that double-linked lists are inherently unsafe, it is (just) Rust ownership model cannot represent them (any other cyclic structures).
It's not that it cannot, it just doesn't want to :-) (but you're right). I guess that in this very case of DLL, it's a bit hard to swallow. To be honest, it's because the rest of rust really helps me in other areas of my projects that I have accepted that. Learning the ownership model of rust is really painful, it really forces you to code in its way and it was not pleasant to me.
I've been trying to convert to Rust an in-memory database and failed. It is strictly single-threaded and full of intrusive lists. I tried hard to please borrow-checker, but one have little choice when internal structures are full of cycles. The result was ugly mess of Rc all over the place. I guess it is just an example of a problem that doesn't fit Rust well.
This makes me wonder: what performance cost Rust code pay due to inability represent cyclic structures efficiently? It seems people tend to design their data in way to please Rust and not in way that would be otherwise more performance efficient.
You can do it by combining ghostcell/qcell along with some bespoke "static/compile-time reference counting" for the double links part. But ghostcell/qcell is quite difficult to use with Rust's current feature set (it has to use lifetime hacks to place a safe "brand" on type instantiations, a kind of quasi-capability construct), so it hasn't become a part of standard rust so far.
A container class that needs cooperation from the contained items, usually with special data fields. For example, a doubly linked list where the forward and back pointers are regular member variables of the contained items. Intrusive containers can avoid memory allocations (which can be a correctness issue in a kernel) and go well with C's lack of built-in container classes. They are somewhat common in C and very rare in C++ and Rust.
At least for a double linked list you can probably get pretty far in terms of performance in the non-intrusive case, if your compiler unboxes the contained item into your nodes? Or are there benefits left in intrusive data structures that this doesn't capture?
Storing the data in nodes doesn't work if the given structure may need to be in multiple linked lists, which iirc was a concern for the kernel?
And generally I'd imagine it's quite a weird form for data structures for which being in a linked list isn't a core aspect (no clue what specifically the kernel uses, but I could imagine situations where where objects aren't in any linked list for 99% of time, but must be able to be chained in even if there are 0 bytes of free RAM ("Error: cannot free memory because memory is full" is probably not a thing you'd ever want to see)).
> Storing the data in nodes doesn't work if the given structure may need to be in multiple linked lists, which iirc was a concern for the kernel?
That's a great point! A language almost like C plus a smart enough compiler could do the unboxing, but this technique doesn't work for multiple structures.
> And generally I'd imagine it's quite a weird form for data structures for which being in a linked list isn't a core aspect (no clue what specifically the kernel uses, but I could imagine situations where where objects aren't in any linked list for 99% of time, but must be able to be chained in even if there are 0 bytes of free RAM ("Error: cannot free memory because memory is full" is probably not a thing you'd ever want to see)).
I think you are right in practice, though in principle you could pre-allocate the necessary memory when you create the items? When you have the intrusive links, you pay for their allocation already anyway, too. In terms of total storage space, you wouldn't pay for more.
(And you don't have to formally alloc them via a call to kmalloc or so. In the sense that you don't need to find a space for them: you just need to make sure that the system keeps a big enough buffer of contiguous-enough space somewhere. Similar to how many filesystems allow you to reserve space for root, but that doesn't mean any particular block is reserved for root up-front.)
But as I thought, that's about in-principle memory usage. A language like C makes the alternative of intrusive data structures much simpler.
> you just need to make sure that the system keeps a big enough buffer of contiguous-enough space somewhere. Similar to how many filesystems allow you to reserve space for root
Said file system reserving can fill up (I've experienced that :) ). Could perhaps engineer some super-equation for a global reserved memory size that can actually guarantee being sufficient; but, unless you mark allocations as needing reserved space vs not and compute based on that (which is back to overhead), that'll necessarily waste a good amount of memory.
And such a global equation of course can result in global failure if just one subsystem miscalculates its required reserved amount, or the calculations improperly account for worst-case non-contiguousness or interleaved different-size-linked-list-alloc-requests or whatever. (never mind that you now need to alloc when adding elements to linked lists, an action that would otherwise be 1-4 inlined stores)
Probably (?) possible to handle, but way way way more easy to break or get wrong (with very-difficult-to-debug consequences) than just some pointer fields.
Oh and also of course, if you store list data in a separate allocation from the main object, you lose the ability to do O(1) (and extremely-cheap at that) "remove self from linked list just given my own pointer", which is probably quite common (and you can't even O(1) with an arraylist instead of a linkedlist).
Haskell's GHC partially does it. LLVM can do it in principle, if your frontend gives enough information. Some JVMs can partially do some of it.
The above is about the optimiser figuring out whether to box or unbox by itself.
If you are willing to give the compiler a hand: Rust can do it just fine and it's the default when you define data structures. If you need boxing, you need to explicitly ask for it, eg via https://doc.rust-lang.org/std/boxed/struct.Box.html
The main thing is that he object can be a member of various structures. It can be in big general queue and in priority queue for example. Once you find it and deal with it you can remove it from both without needing to search for it.
Same story for games where it can be in the list of all objects and in the list of objects that might be attacked. Once it's killed you can remove it from all lists without searching for it in every single one.
Yeah, if you need a linked list (you probably don't) use that. If however you are one of the very small number of people who need fine-grained control over a tailored data-structure with internal cross-references or whatnot then you may find yourself in a world where Rust really does not believe that you know what you are doing and fights you every step of the way. If you actually do know what you are doing, then Zig is probably the best modern choice. The TigerBeetle people chose Zig for these reasons, various resources on the net explain their motivations.
The point with the linked list is that it is perfectly valid to use unsafe to design said ”tailored data structure with internal cross-reference or what not” library and then expose a safe interface.
If you’re having trouble designing a safe interface for your collection then that should be a signal that maybe what you are doing will result in UB when looked at the wrong way.
That is how all standard library collections in Rust works. They’ve just gone to the length of formally verifying parts of the code to ensure performance and safety.
> If you’re having trouble designing a safe interface for your collection then that should be a signal that maybe what you are doing will result in UB when looked at the wrong way.
Rust is great, but there are some things that are safe (and you could prove them safe in the abstract), but that you can't easily express in Rust's type system.
More specifically, there are some some things and usage pattern of these things that are safe when taken together. But the library can't force the safe usage pattern on the client, with the tools that Rust provides.
If you can't create a safe interface and must have the function then create an unsafe function and clearly document the invariants and then rely on the user to uphold them?
Take a look at the unsafe functions for the standard library Vec type to see examples of this:
> If you can't create a safe interface and must have the function then create an unsafe function and clearly document the invariants and then rely on the user to uphold them?
Yes, that's what you do in practice. But it's no different--in principle--from the approach C programmers have to use.
>>That is how all standard library collections in Rust works
Yeah and that's what not going to work for high performance data structures because you need to embed hooks into the objects - not just put objects into a bigger collection object. Once you think in terms of a collection that contains things you have already lost that specific battle.
Another thing that doesn't work very well in Rust (from my understanding, I tried it very briefly) is using multiple memory allocators which is also needed in high performance code. Zig takes care to make it easy and explicit.
It’s okay to enjoy driving an outdated and dangerous car for the thrill because it makes pleasing noise, as long as you don’t annoy too much other people with it.
My current project is C++ backend. I do a lot of debugging but all of it concerns business logic, some scientific calculations and the likes. In this situations having Rust will give me exactly zero benefits. As for "safety". I am a practical man and I pay my own money for development. Being able to use modern C++ I have forgotten when was the last time I had any memory related issue. My backends run for years serving many customers with no complaints in this department. Does not mean of course they're really really safe but I sleep well ;)
If it wasn't clear, I have to debug Rust code waaaay less than C, for two reasons:
1. Memory safety - these can be some of the worst bugs to debug in C because they often break sane invariants that you use for debugging. Often they break the debugger entirely! A classic example is forgetting to return a value from a non-void function. That can trash your stack and end up causing all sorts of impossible behaviours in totally different parts of the code. Not fun to debug!
2. Stronger type system - you get an "if it compiles it works" kind of experience (as in Haskell). Obviously that isn't always the case, but I can sometimes write several hundred lines of Rust and once it's compiling it works first time. I've had to suppress my natural "oh I must have forgotten to save everything or maybe incremental builds are broken or something" instinct when this happens.
Net result is that I spend at least 10x less time in a debugger with Rust than I do with C.
Before we ask if almost all things old will be rewritten in Rust, we should ask if almost all things new are being written in Rust or other memory-safe languages?
Obviously not. When will that happen? 15 years? Maybe it's generational: How long before developers 'born' into to memory-safe languages as serious choices will be substantially in charge of software development?
I don't know I tend to either come across new tools written in Rust, JavaScript or Python but relatively low amount of C. The times I see some "cargo install xyz" in a git repo of some new tool is definitely noticeable.
I'm a bit wary if this is hiding an agist sentiment, though. I doubt most Rust developers were 'born into' the language, but instead adopted it on top of existing experience in other languages.
People can learn Rust at any age. The reality is that experienced people often are more hesitant to learn new things.
I can think of possible reasons: Early in life, in school and early career, much of what you work on is inevitably new to you, and also authorities (professor, boss) compel you to learn whatever they choose. You become accustomed to and skilled at adapting new things. Later, when you have power to make the choice, you are less likely to make yourself change (and more likely to make the junior people change, when there's a trade-off). Power corrupts, even on that small scale.
There's also a good argument for being stubborn and jaded: You have 30 years perfecting the skills, tools, efficiencies, etc. of C++. For the new project, even if C++ isn't as good a fit as Rust, are you going to be more efficient using Rust? How about in a year? Two years? ... It might not be worth learning Rust at all; ROI might be higher continuing to invest in additional elite C++ skills. Certainly that has more appeal to someone who knows C++ intimately - continue to refine this beautiful machine, or bang your head against the wall?
For someone without that investment, Rust might have higher ROI; that's fine, let them learn it. We still need C++ developers. Morbid but true, to a degree: 'Progress happens one funeral at a time.'
> experienced people often are more hesitant to learn new things
I believe the opposite. There's some kind of weird mentality in beginner/wannabe programmers (and HR, but that's unrelated) that when you pick language X then you're an X programmer for life.
Experienced people know that if you need a new language or library, you pick up a new language or library. Once you've learned a few, most of them aren't going to be very different and programming is programming. Of course it will look like work and maybe "experienced" people will be more work averse and less enthusiastic than "inexperienced" (meaning younger) people.
I agree that "programming is programming" but Rust feels very different with my background (some ML and many years of C, Java, some Python, a little Go, etc.) than for somebody whose only previous language is Java, or Javascript, or perhaps even C++
The "You can write Java in any language" mentality afflicts some languages worse than others, but if your programming is exclusively in a single language you will be tainted by that regardless of the language. C++ is perhaps worst for this because its proponents, and
indeed its standards committee have their own terminology for everything. So there aren't "methods" but instead "non-static member functions" for example. This has the "Call a rabbit a smeerp" problem, where you can't tell whether you actually don't know a feature or if you just know the exact same feature by a different name.
I guess what I'm saying is that writing any language in an idiomatic way takes a bit more than just "programming is programming" plus a word-for-word translation guide, and some people might be weary of learning new idioms.
>Experienced people know that if you need a new language or library, you pick up a new language or library.
That heavily depends, if you tap into a green field project, yes. Or free reign over a complete rewrite of existing projects. But these things are more the exception than the regular case.
Even on green field project, ecosystem and available talents per framework will be a consideration most of the time.
There are also other things like being parent and wanting to take care of them that can come into consideration later in life. So more like more responsibilities constraints perspectives and choices than power corrupts in purely egoistic fashion.
I still think you're off the mark. Again, most existing Rust developers are not "blank slate Rust developers". That they do not rush out to rewrite all of their past projects in C++ may be more about sunk costs, and wanting to solve new problems with from-scratch development.
> most existing Rust developers are not "blank slate Rust developers"
Not most, but the pool of software devs has been doubling every five years, and Rust matches C# on "Learning to Code" voters at Stack Overflow's last survey, which is crazy considering how many people learn C# just to use Unity. I think you underestimate how many developers are Rust blank slates.
Anecdotically, I've recently come across comments from people who've taught themselves Rust but not C or C++.
Oh I agree the survey has issues, I was just thinking about how each year the stats get more questionable! I just think it shows that interest in Rust doesn't come only from people with a C++ codebase to rewrite. Half of all devs have got less than five years of experience with any toolchain at all, let alone C++, yet many want to give Rust a try. I do think there will be a generational split there.
> Steve Klabnik?
Thankfully no. I've actually argued with him a couple times. Usually in partial agreement, but his fans will downvote anyone who mildly disagrees with him.
Also, I'm not even big on Rust: every single time I've tried to use it I instinctively reached for features that turned out to be unstable, and I don't want to deal with their churn, so I consider the language still immature.
Okay I had upvoted you but now you're just being an asshole. Predictable from someone on multiple fucking throwaways created just to answer on a single post and crap on a piece of tech I suppose; I don't even care much about Rust. And I'm sorry to inform you I'm not Klabnik, but delusions are free: Maybe you think everyone using -nik is actually the same person and you've uncovered a conspiracy. Congrats on that.
I'd shove you a better data point but people aren't taking enough surveys for our sake, that's the one we've got. Unless you want to go with Jetbrains', which, spoilers, skews towards technologies supported by Jetbrains; I'm not aware of other major ones.
This behavior is weird. Your parent writes nothing like me.
The only alt I’ve made on hacker news is steveklabnik1, or whatever I called it, because I had locked myself out of this account. pg let me back in and so I stopped using it.
According to the strange data at https://survey.stackoverflow.co/2025/technology#most-popular... , 44.6% have responded positively to that question regarding C++. But there may be some issues, for the question involves two check boxes, yet there is only one statistic.
Sure there are plenty of them, hence why you seem remarks like wanting to use Rust but with a GC, or assigned to Rust features that most ML derived languages have.
New high-scale data infrastructure projects I am aware of mostly seem to be C++ (often C++20). A bit of Rust, which I’ve used, and Zig but most of the hardcore stuff is still done in C++ and will be for the foreseeable future.
It is easy to forget that the state-of-the-art implementations of a lot of systems software is not open source. They don’t struggle to attract contributors because of language choices, being on the bleeding edge of computer science is selling point enough.
There's a "point of no return" when you start to struggle to hire anyone on your teams because no one knows the language and no one is willing to learn. But C++ is very far from it.
There's always someone willing to write COBOL for the right premium.
I'm working on Rust projects, so I may have incomplete picture, but I'm from what I see when devs have a choice, they prefer working with Rust over C++ (if not due to the language, at least due to the build tooling).
Writing C++ is easier than writing Rust. But writing safe multithreaded code in C++?
I don't want to write multithreaded C++ at all unless I explicitly want a new hole in my foot. Rust I barely have any experience with, but it might be less frustrating than that.
Anecdotally, my "wow, this Rust business might really go somewhere" moment was when I tried adding multithreading to a random tool I made (dispatching tasks to new threads).
Multithreading had not been planned or architected for, it took 30 min, included the compiler informing me I couldn't share a hashmap with those threads unsynchronised, and informing me on how to fix it.
I've had similar experience when the compiler immediately found unsynchronized state deep inside a 3rd party library I've been using. It was a 5-minute fix for what otherwise could have been mysterious unreproducible data corruption.
These days even mobile phones have multicore CPUs, so it's getting hard to find excuses for single-threaded programs.
Game development, graphics and VFX industry, AI tooling infrastructure, embedded development, Maker tools like Arduino and ESP32, compiler development.
> ... has a debug allocator that maintains memory safety in the face of use-after-free and double-free
which is probably true (in that it's not possible to violate memory safety on the debug allocator, although it's still a strong claim). But beyond that there isn't really any current marketing for Zig claiming safety, beyond a heading in an overview of "Performance and Safety: Choose Two".
It is intended for release builds. The ReleaseSafe target will keep the checks. ReleaseFast and ReleaseSmall will remove the checks, but those aren't the recommended release modes for general software. Only for when performance or size are critical.
Yes it does, alongside C++, Rust isn't available everywhere.
There are industry standards based in C, or C++, and I doubt they will be adopting Rust any time soon.
POSIX (which does require a C compiler for certification), OpenGL, OpenCL, SYSCL, Aparavi, Vulkan, DirectX, LibGNM(X), NVN, CUDA, LLVM, GCC, Unreal, Godot, Unity,...
Then plenty of OSes like the Apple ecosystem, among many other RTOS and commercial endevours for embedded.
Also the official Rust compiler isn't fully bootstraped yet, thus it depends on C++ tooling for its very existence.
Which is why efforts to make C and C++ safer are also much welcomed, plenty of code out there is never going to be RIR, and there are domains where Rust will be a runner up for several decades.
I agree with you for the most part, but it's worth noting that those standards can expose a C API/ABI while being primarily implemented in and/or used by non-C languages. I think we're a long way away from that, but there's no inherent reason why C would be a going concern for these in the Glorious RIIR Future:tm:.
It’s available on more obscure platforms than Rust, and more people are familiar with it.
I wouldn’t say it’s inevitable that everything will be rewritten in Rust, at the very least this will this decades. C has been with us for more than half a century and is the foundation of pretty much everything, it will take a long time to migrate all that.
More likely is that they will live next to each other for a very, very long time.
Rust still compiles into bigger binary sizes than C, by a small amount. Although it’s such a complex thing depending on your code that it really depends case-by-case, and you can get pretty close. On embedded systems with small amounts of ram (think on the order of 64kbytes), a few extra kb still hurts a lot.
Lack of stable build-std and panic_immediate_abort features result in a order of magnitude size difference for some rust code I have. Using no_std brings it to ~60kb from ~350kb, but still more than the ~40kb for the C version
Afaik despite Rust not having exceptions, panic still unwinds the stack and executes 'drop'-s the same way C++ does, which means the code retains much of the machinery necessary to support exceptions.
The panic_immediate_abort feature should prevent this. build-std is the name of the feature that recompiles the Rust standard libraries with your chosen profile, so the combination of these two features should result in no unwinding code bloat.
It's possible for a crate to assume there will be unwinding, but given the Rust communities general attitude to exceptions it's not common.
By default, all it means is that unwinding the stack doesn't happen, so Drop implementations don't get called. Which _can_ result in bugs, but they're a fairly familiar kind: it's the same kind of fault if a process is aborted (crashes?), and it doesn't get a chance to finalise external resources.
EDIT: to clarify, the feature isn't really something crates have to opt in to support
Apple handled this problem by adding memory safety to C (Firebloom). It seems unlikely they would throw away that investment and move to Rust. I’m sure lots of other companies don’t want to throw away their existing code, and when they write new code there will always be a desire to draw on prior art.
That's a rather pessimistic take compared to what's actually happening. What you say should apply to the big players like Amazon, Google, Microsoft, etc the most, because they arguably have massive C codebases. Yet, they're also some of the most enthusiastic adopters and promoters of Rust. A lot of other adopters also have legacy C codebases.
I'm not trying to hype up Rust or disparage C. I learned C first and then Rust, even before Rust 1.0 was released. And I have an idea why Rust finds acceptance, which is also what some of these companies have officially mentioned.
C is a nice little language that's easy to learn and understand. But the price you pay for it is in large applications where you have to handle resources like heap allocations. C doesn't offer any help there when you make such mistakes, though some linters might catch them. The reason for this, I think, is that C was developed in an era when they didn't have so much computing power to do such complicated analysis in the compiler.
People have been writing C for ages, but let me tell you - writing correct C is a whole different skill that's hard and takes ages to learn. If you think I'm saying this because I'm a bad programmer, then you would be wrong. I'm not a programmer at all (by qualification), but rather a hardware engineer who is more comfortable with assembly, registers, Bus, DRAM, DMA, etc. I still used to get widespread memory errors, because all it takes is a lapse in attention while coding. That strain is what Rust alleviates.
Not trying to make a value judgement on Rust either, just brainstorming why Rust changeover might go slow per the question.
FWIW I work in firmware with the heap turned off. I’ve worked on projects in both C and Rust, and agree Rust still adds useful checks (at the cost of compile times and binary sizes). It seems worth the trade off for most projects.
I'm curious why your perspective on Rust as a HW engineer. Hardware does a ton of things - DMA, interrupts, etc. that are not really compatible with Rust's memory model - after all Rust's immutable borrows should guarantee the values you are reading are not aliased by writers and should be staying constant as long as the borrow exists.
This is obviously not true when the CPU can either yank away the execution to a different part of the program, or some foreign entity can overwrite your memory.
Additionally, in low-level embedded systems, the existence of malloc is not a given, yet Rust seems to assume you can dynamically allocate memory with a stateless allocator.
Rust has no_std to handle not having an allocator.
Tons of things end up being marked “unsafe” in systems/embedded Rust. The idea is you sandbox the unsafeness. Libraries like zero copy are a good example of “containing” unsafe memory accesses in a way that still gets you as much memory safety as possible given the realities of embedded.
Tl;dr you don’t get as much safety as higher level code but you still get more than C. Or maybe put a different way you are forced to think about the points that are inherently unsafe and call them out explicitly (which is great when you think about how to test the thing)
Dunno. I used to do embedded. A ton of (highly capable) systems have tiny amounts of RAM (like 32kb-128kb). Usually these controllers run a program in an infinite event loop (with perhaps a sleep in the end of the iteration), communication with peripherals is triggered via interrupts. There's no malloc, memory is pre-mapped.
This means:
- All lifetimes are either 'stack' or 'forever'
- Thread safety in the main loop is ensured by disabling interrupts in the critical section - Rust doesn't understand this paradigm (it can be taught to, I'm sure)
- Thread safety in ISRs should also be taught to Rust
- Peripherals read and write to memory via DMA, unbekownst to the CPU
etc.
So all in all, I don't see Rust being that much more beneficial (except for stuff like array bounds checking).
I'm sure you can describe all these behaviors to Rust so that it can check your program, but threading issues are limited, and there's no allocation, and no standard lib (or much else from cargo).
Rust may be a marginally better language than C due to some convenience features, but I feel like there's too much extra work very little benefit.
Hubris is an embedded microkernel targeting the kilobytes-to-megabyte range of RAM. In Rust. They're saying very positive things about the bug prevention aspects.
You handle DMA the same way as on any other system - e.x. you mark the memory as system/uncached (and pin it if you have virtual memory on), you use memory barriers when you are told data has arrived to get the memory controller up to speed with what happened behind its back, and so on. You’re still writing the same code that controls the hardware in the same way, nothing about that is especially different in C vs Rust.
I think there is very much a learning curve, and that friction is real - hiring people and teaching them Rust is harder than just hiring C devs. People on HN tend to take this almost like a religious question - “how could you dare to write memory unsafe code in 2025? You’re evil!” But pragmatically this is a real concern.
> I'm curious why your perspective on Rust as a HW engineer.
C, C++ and Rust requires you to know at least the basics of the C memory model. Register variables, heap, stack, stack frame, frame invalidation, allocators and allocation, heap pointer invalidation, etc. There are obviously more complicated stuff (which I think you already know, seeing that you're an embedded developer), but this much is necessary to avoid common memory errors like memory leaks (not a safety error), use-after-free, double-free, data-race, etc. This is needed for even non-system programs and applications, due to lack of runtime memory management (GC or RC). You can get by, by following certain rules of thumb in C and C++. But to be able to write flawless code, you have to know those hardware concepts. This is where knowledge of process and memory architecture comes in handy. You start with the fundamental rule before programming, instead of the other way around that people normally take. Even in Rust, the complicated borrow checker rules start to make sense once you realize how they help you overcome the mistakes you can make with the hardware.
> Hardware does a ton of things - DMA, interrupts, etc. that are not really compatible with Rust's memory model - after all Rust's immutable borrows should guarantee the values you are reading are not aliased by writers and should be staying constant as long as the borrow exists.
> This is obviously not true when the CPU can either yank away the execution to a different part of the program, or some foreign entity can overwrite your memory.
I do have an answer, but I don't think it can be explained in a better way than how @QuiEgo did it: You can 'sandbox' those unsafe parts within Rust unsafe blocks. As I have explained elsewhere, these sandboxed parts are surprisingly small even in kernel or embedded code (please see the Rust standard library for examples). As long as you enforce the basic correctness conditions (the invariants) inside the unsafe blocks, the rest of the code is guaranteed to be safe. And even if you do make a mistake there (i.e memory safety), they are easier to find because there's very little code there to check. Rust does bring something new to the table for the hardware.
NOTE: I believe that those parts in the kernel are still in C. Rust is just a thin wrapper over it for writing drivers. That's a reasonable way forward.
> Additionally, in low-level embedded systems, the existence of malloc is not a given, yet Rust seems to assume you can dynamically allocate memory with a stateless allocator.
So you try to say c is for good programmers only and rust let also the idiots Programm? I think that’s the wrong way to argue for rust. Rust catches one kind of common problem but but does not magically make logic errors away.
> It seems unlikely [Apple] would throw away that investment and move to Rust.
Apple has invested in Swift, another high level language with safety guarantees, which happens to have been created under Chris Lattner, otherwise known for creating LLVM. Swift's huge advantage over Rust, for application and system programming is that it supports an ABI [1] which Rust, famously, does not (other than falling back to a C ABI, which degrades its promises).
[1] for more on that topic, I recommend this excellent article: https://faultlore.com/blah/swift-abi/ Side note, the author of that article wrote Rust's std::collections API.
Swift does not seem suitable for OS development, at least not as much as C or C++.[0] Swift handles by default a lot of memory by using reference counting, as I understand it, which is not always suitable for OS development.
[0]: Rust, while no longer officially experimental in the Linux kernel, does not yet have major OSs written purely in it.
The practical reality is arguably more important than beliefs. Apple has, as it turns out, invested in trying to make Swift more suitable for kernel and similar development, like trying to automate away reference counting when possible, and also offering Embedded Swift[0], an experimental subset of Swift with significant restrictions on what is allowed in the language. Maybe Embedded Swift will be great in the future, and it is true that Apple investing into that is significant, but it doesn't seem like it's there.
> Embedded Swift support is available in the Swift development snapshots.
And considering Apple made Embedded Swift, even Apple does not believe that regular Swift is suitable. Meaning that you're undeniably completely wrong.
You show a lack of awareness that ISO C and C++ are also not applicable, because on those domains the full ISO language standard isn't available, which is why freestanding is a thing.
But at least a lot of tasks in a kernel would require something else than reference counting, unless it can be guaranteed that the reference counting is optimized away or something, right?
There are some allocations where it doesn't make sense for them to have multiple owners (strong references) but I wouldn't say it makes sense to think about it as being optimized away or not.
Is this even a fair question? A common response to pointing out that Oberon and the other Wirth languages where used to write several OS’s (using full GC in some cases) is that they don’t count, just like Minix doesn’t count for proof of microkernels. The prime objection being they are not large commercial OS’s. So, if the only two examples allowed are Linux and Windows (and maybe MacOS) then ‘no’ there are no wide spread, production sized OS’s that use GC or reference counting.
The big sticking point for me is that for desktop and server style computing, the hardware capabilities have increased so much that a good GC would for most users be acceptable at the kernel level. The other side to that coin is that then OS’s would need to be made on different kernels for large embedded/tablet/low-power/smart phone use cases. I think tech development has benefitted from Linux being used at so many levels.
A push to develop a new breed of OS, with a microkernel and using some sort of ‘safe’ language should be on the table for developers. But outside of proprietary military/finance/industrial (and a lot of the work in these fields are just using Linux) areas there doesn’t seem to be any movement toward movement toward a less monolithic OS situation.
It's a bit like asking if there is any significant advantage to ICE motors over electric motors. They both have advantages and disadvantages. Every person who uses one or the other, will tell you about their own use case, and why nobody could possibly need to use the alternative.
There's already applications out there for the "old thing" that need to be maintained, and they're way too old for anyone to bother with re-creating it with the "new thing". And the "old thing" has some advantages the "new thing" doesn't have. So some very specific applications will keep using the "old thing". Other applications will use the "new thing" as it is convenient.
To answer your second question, nothing is inevitable, except death, taxes, and the obsolescence of machines. Rust is the new kid on the block now, but in 20 years, everybody will be rewriting all the Rust software in something else (if we even have source code in the future; anyone you know read machine code or punch cards?). C'est la vie.
I think it'll be less like telegram lines- which were replaced fully for a major upgrade in functionality, and more like rail lines- which were standardized and ubiquitous, still hold some benefit but mainly only exist in areas people don't venture nearly as much.
Shitloads of already existing libraries. For example I'm not going to start using it for Arduino-y things until all the peripherals I want have drivers written in Rust.
That's not really the case. Not all C APIs are inherently unsafe by construction, and I've always appreciated when someone has wrapped a C library and produced two crates:
- a pure binding crate, which exposes the C lib libraries API, and
- a wrapper library that performs some basic improvements
Stuff in the second category typically includes adding Drop impls to resources that need to be released, translating "accepts pointer + len" into "accepts slices" (or vice versa on return), and "check return value of C call and turn it into a Result, possibly with a stringified error".
All of those are also good examples of local reasoning about unsafety. If a C API returns a buffer + size, it's unsafe to turn it into a reference/slice. But if you check the function succeeded, you unsafely make the slice/reference, and return it from a safe function. If it crashes, you've either not upheld the C calls preconditions (your fault, check how to call the C function), or the C code has a bug (not your fault, the bug is elsewhere).
Rust has a nice compiler-provided static analyzer using annotation to do life time analysis and borrow checking. I believe borrow checking to be a local optima trap when it comes to static analysis and finds it often more annoying to use than I would like but I won't argue it's miles better than nothing.
C has better static analysers available. They can use annotations too. Still, all of that is optional when it's part of Rust core language. You know that Rust code has been successfully analysed. Most C code won't give you this. But if it's your code base, you can reach this point in C too.
C also has a fully proven and certified compiler. That might be a requirement if you work in some niche safety critical applications. Rust has no equivalent there.
The discussion is more interesting when you look at other languages. Ada/Spark is for me ahead of Rust both in term of features and user experience regarding the core language.
Rust currently still have what I consider significant flaws: a very slow compiler, no standard, support for a limited number of architectures (it's growing), it's less stable that I consider it should be given its age, and most Rust code tends to use more small libraries than I would personaly like.
Rust is very trendy however and I don't mean that in a bad way. That gives it a lot of leverage. I doubt we will ever see Ada in the kernel but here we are with Rust.
you can look at rust sources of real system programs like framekernel or such things. uefi-rs etc.
there u can likely explore well the boundaries where rust does and does not work.
people have all kind of opinions. mine is this:
if you need unsafe peppered around, the only thing rust offers is being very unergonomic. its hard to write and hard to debug for no reason. Writing memory-safe C code is easy. The problems rust solves arent bad, just solved in a way thats way more complicated than writing same (safe) C code.
a language is not unsafe. you can write perfectly shit code in rust. and you can write perfectly safe code in C.
people need to stop calling a language safe and then reimplementing other peoples hard work in a bad way creating whole new vulnerabilities.
I disagree. Rust shines when you need perform "unsafe" operations. It forces programmers to be explicit and isolate their use of unsafe memory operations. This makes it significantly more feasible to keep track of invariants.
It is completely besides the point that you can also write "shit code" in Rust. Just because you are fed up with the "reimplement the world in Rust" culture does not mean that the tool itself is bad.
1. This doesn't really matter for the argument. Most of the time you can audit unsafe blocks, in some instances the invariants you are upholding require you to consider more code. The benefit is still that you can design safe interfaces around these smaller bits of audited code.
2. I agree it's harder, I feel like most of the community knows and recognises this.
> Maybe Rust proponents should spend more time on making unsafe Rust easier and more ergonomic, instead of effectively *undermining safety and security*. Unless the strategy is to trick as many other people as possible into using Rust, and then hope those people fix the issues for you, a common strategy normally used for some open source projects, not languages.
I don't think there is any evidence that Rust is undermining safety and security. In fact all evidence shows it's massively improving these metrics wherever Rust replaces C and C++ code. If you have evidence to the contrary let's see it.
For my hobby code, I'm not going to start writing Rust anytime soon. My code is safe enough and I like C as it is. I don't write software for martian rovers, and for ordinary tasks, C is more ergonomic than Rust, especially for embedded tasks.
For my work code, it all comes down to SDKs and stuff. For example I'm going to write firmware for Nordic ARM chip. Nordic SDK uses C, so I'm not going to jump through infinite number of hoops and incomplete rust ports, I'll just use official SDK and C. If it would be the opposite, I would be using Rust, but I don't think that would happen in the next 10 years.
Just like C++ never killed C, despite being perfect replacement for it, I don't believe that Rust would kill C, or C++, because it's even less convenient replacement. It'll dilute the market, for sure.
I don’t agree that C++ is a bad language, though it has been standardized to death into a bad language. But the whole point is for C++ to not be worse than C while offering a lot more, which I think it does well. Of course, my last serious use of C++ was a little after release E…
A lot of C's popularity is with how standard and simple it is. I doubt Rust will be the safe language of the future, simply because of its complexity. The true future of "safe" software is already here, JavaScript.
There will be small niches leftover:
* Embedded - This will always be C. No memory allocation means no Rust benefits. Rust is also too complex for smaller systems to write compilers.
* OS / Kernel - Nearly all of the relevant code is unsafe. There aren't many real benefits. It will happen anyways due to grant funding requirements. This will take decades, maybe a century. A better alternative would be a verified kernel with formal methods and a Linux compatibility layer, but that is pie in the sky.
* Game Engines - Rust screwed up its standard library by not putting custom allocation at the center of it. Until we get a Rust version of the EASTL, adoption will be slow at best.
* High Frequency Traders - They would care about the standard library except they are moving on from C++ to VHDL for their time-sensitive stuff. I would bet they move to a garbage-collected language for everything else, either Java or Go.
* Browsers - Despite being born in a browser, Rust is unlikely to make any inroads. Mozilla lost their ability to make effective change and already killed their Rust project once. Google has probably the largest C++ codebase in the world. Migrating to Rust would be so expensive that the board would squash it.
* High-Throughput Services - This is where I see the bulk of Rust adoption. I would be surprised if major rewrites aren't already underway.
This isn't really true; otherwise, there would be no reason for no_std to exist. Data race safety is independent of whether you allocate or not, lifetimes can be handy even for fixed-size arenas, you still get bounds checks, you still get other niceties like sum types/an expressive type system, etc.
> OS / Kernel - Nearly all of the relevant code is unsafe.
I think that characterization is rather exaggerated. IIRC the proportion of unsafe code in Redox OS is somewhere around 10%, and Steve Klabnik said that Oxide's Hubris has a similarly small proportion of unsafe code (~3% as of a year or two ago) [0]
> Browsers - Despite being born in a browser, Rust is unlikely to make any inroads.
Technically speaking, Rust already has. There has been Rust in Firefox for quite a while now, and Chromium has started allowing Rust for third-party components.
> Google has probably the largest C++ codebase in the world. Migrating to Rust would be so expensive that the board would squash it.
Google is transitioning large parts of Android to Rust and there is now first-party code in Chromium and V8 in Rust. I’m sure they’ll continue to write new C++ code for a good while, but they’ve made substantial investments to enable using Rust in these projects going forward.
Also, if you’re imagining the board of a multi-trillion dollar market cap company is making direct decisions about what languages get used, you may want to check what else in this list you are imagining.
Unless rules have changed Rust is only allowed a minor role in Chrome.
> Based on our research, we landed on two outcomes for Chromium.
> We will support interop in only a single direction, from C++ to Rust, for now. Chromium is written in C++, and the majority of stack frames are in C++ code, right from main() until exit(), which is why we chose this direction. By limiting interop to a single direction, we control the shape of the dependency tree. Rust can not depend on C++ so it cannot know about C++ types and functions, except through dependency injection. In this way, Rust can not land in arbitrary C++ code, only in functions passed through the API from C++.
> We will only support third-party libraries for now. Third-party libraries are written as standalone components, they don’t hold implicit knowledge about the implementation of Chromium. This means they have APIs that are simpler and focused on their single task. Or, put another way, they typically have a narrow interface, without complex pointer graphs and shared ownership. We will be reviewing libraries that we bring in for C++ use to ensure they fit this expectation.
Also even though Rust would be a safer alternative to using C and C++ on the Android NDK, that isn't part of the roadmap, nor the Android team provides any support to those that go down that route. They only see Rust for Android internals, not app developers
If anything, they seem more likely to eventually support Kotlin Native for such cases than Rust.
The V8 developers seem more excited to use it going forward, and I’m interested to see how it turns out. A big open question about the Rust safety model for me is how useful it is for reducing the kind of bugs you see in a SOTA JIT runtime.
> They only see Rust for Android internals, not app developers
I’m sure, if only because ~nobody actually wants to write Android apps in Rust. Which I think is a rational choice for the vast majority of apps - Android NDK was already an ugly duckling, and a Rust version would be taken even less seriously by the platform.
That looks only to be the guidelines on how to integrate Rust projects, not that the policy has changed.
The NDK officially it isn't to write apps anyway, people try to do that due to some not wanting to touch Java or Kotlin, but that has never been the official point of view from Android team since it was introduced in version 2, rather:
> The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input. The NDK may not be appropriate for most novice Android programmers who need to use only Java code and framework APIs to develop their apps. However, the NDK can be useful for cases in which you need to do one or more of the following:
> Squeeze extra performance out of a device to achieve low latency or run computationally intensive applications, such as games or physics simulations.
> Reuse your own or other developers' C or C++ libraries.
So it would be expected that at least for the first scenario, Rust would be a welcomed addition, however as mentioned they seem to be more keen into supporting Kotlin Native for it.
There are memory safety issues that literally only apply to memory on the stack, like returning dangling pointers to local variables. Not touching the heap doesn't magically avoid all of the potential issues in C.
Rust is already making substantial inroads in browsers, especially for things like codecs. Chrome also recently replaced FreeType with Skrifa (Rust), and the JS Temporal API in V8 is implemented in Rust.
> Embedded - This will always be C. No memory allocation means no Rust benefits. Rust is also too complex for smaller systems to write compilers.
Embedded Dev here and I can report that Rust has been more and more of a topic for me. I'm actually using it over C or C++ in a bare metal application. And I don't get where the no allocation -> no benefit thing comes from, Rust gives you much more to work with on bare metal than C or C++ do.
Memory safety applies to all memory. Not just heap allocated memory.
This is a strange claim because it's so obviously false. Was this comment supposed to be satire and I just missed it?
Anyway, Rust has benefits beyond memory safety.
> Rust is also too complex for smaller systems to write compilers.
Rust uses LLVM as the compiler backend.
There are already a lot of embedded targets for Rust and a growing number of libraries. Some vendors have started adopting it with first-class support. Again, it's weird to make this claim.
> Nearly all of the relevant code is unsafe. There aren't many real benefits.
Unsafe sections do not make the entire usage of Rust unsafe. That's a common misconception from people who don't know much about Rust, but it's not like the unsafe keyword instantly obliterates any Rust advantages, or even all of its safety guarantees.
It's also strange to see this claim under an article about the kernel developers choosing to move forward with Rust.
> High Frequency Traders - They would care about the standard library except they are moving on from C++ to VHDL for their time-sensitive stuff. I would bet they move to a garbage-collected language for everything else, either Java or Go.
C++ and VHDL aren't interchangeable. They serve different purposes for different layers of the system. They aren't moving everything to FPGAs.
Betting on a garbage collected language is strange. Tail latencies matter a lot.
This entire comment is so weird and misinformed that I had to re-read it to make sure it wasn't satire or something.
> Memory safety applies to all memory. Not just heap allocated memory.
> Anyway, Rust has benefits beyond memory safety.
I want to elaborate on this a little bit. Rust uses some basic patterns to ensure memory safety. They are 1. RAII, 2. the move semantics, and 3. the borrow validation semantics.
This combination however, is useful for compile-time-verified management of any 'resource', not just heap memory. Think of 'resources' as something unique and useful, that you acquire when you need it and release/free it when you're done.
For regular applications, it can be heap memory allocations, file handles, sockets, resource locks, remote session objects, TCP connections, etc. For OS and embedded systems, that could be a device buffer, bus ownership, config objects, etc.
> > Nearly all of the relevant code is unsafe. There aren't many real benefits.
Yes. This part is a bit weird. The fundamental idea of 'unsafe' is to limit the scope of unsafe operations to as few lines as possible (The same concept can be expressed in different ways. So don't get offended if it doesn't match what you've heard earlier exactly.) Parts that go inside these unsafe blocks are surprisingly small in practice. An entire kernel isn't all unsafe by any measure.
> Rust is also too complex for smaller systems to write compilers.
I am not a compiler engineer, but I want to tease apart this statement. As I understand, the main Rust compiler uses LLVM framework which uses an intermediate language that is somewhat like platform independent assembly code. As long as you can write a lexer/parser to generate the intermediate language, there will be a separate backend to generate machine code from the intermediate language. In my (non-compiler-engineer) mind, separates the concern of front-end language (Rust) from target platform (embedded). Do you agree? Or do I misunderstand?
> Embedded - This will always be C. No memory allocation means no Rust benefits. Rust is also too complex for smaller systems to write compilers.
Modern embedded isn't your grandpa's embedded anymore. Modern embedded chips have multiple KiB of ram, some even above 1MiB and have been like that for almost a decade (look at ESP32 for instance). I once worked on embedded projects based on ESP32 that used full C++, with allocators, exceptions, ... using SPI RAM and worked great. There's a fantastic port of ESP-IDF on Rust that Espressif themselves is maintaining nowadays, too.
When I was in school there were a handful of people (couple dozen in a student body of ~10k) who did it. They'd use the athletics center for shower and the buildings allowed 24hr key access for bathroom. Most of them had jobs in dining services for the free meal per shift. These people weren't looked at as being poor, they were looked at as hard-assed cheapskates who were toughing out a hard lifestyle and consequently saving money in the process and were generally envied for doing something most couldn't stomach. If you can tolerate it it's a great deal if you're a student because you can use student access to facilities to cover a lot of your other needs and it's truly just a place to sleep and the ~20yo body can tolerate the lifestyle. Motorhomes weren't allowed and they were real touchy about box trucks so minivans and conversion vans were mostly what you'd see though there was the occasional station wagon.
And this was not in a mild climate, wikipedia says the mean daily temp in January is 18deg (freedom). That said, I have no doubt that snootier universities didn't allow these sorts of things and would harass the shit out of you with their PD if you tried this there.
I don't want to bemoan this. There's some people who don't have the resources to house a bunch of people but do have authority over a parking lot. I applaud anyone doing what they can for others with what they've got.
It's just that when this came up shortly after april the first this year, there was nothing to it, only circular references, supported by nice sloppy ai-gfx, and nothing else. It was an assumption/speculation, uttered by someone, endlessly repeated and overamplified.
I don't see any reason why it should be different this time.
I loved the campaigns so much that I spent many dollars to play with the campaign editor in a net bar back then. I never figured out how to recreate the Corsair scene at the beginning of Protoss level 2. It was only after many years that I found out that it requires a script not in the official editor — some modders created a new editor that includes all those “unofficial” scripts.
>how do hybrid schemes work out: some home, some office, less commute overall?
I think it depends on the type of work. I work as a support engineer for business stakeholders. Business stakeholders don't work in "Sprints", and always want to get anything ASAP. In that sense, if I want to maximize my value to the company, in-office is the best.
But frankly, I don't like that, so working remote is the best for me, IN THAT PERSPECTIVE. However, I do love the snacks in office, and I want to keep my job, so hybrid works the best for me. The stakeholders get to bug me from time to time in 3 days per week, and I book as many meetings as I can in those 3 days, and bring a non-fiction just to breath a little better.
I just wish Toronto has cheaper housing though, so I can live closer to the office.
Art Deco has always been one of the best architectural styles, IMHO. I also like the pseudo-classical Greek design often found in American government buildings in small towns (city hall, the public library, and so on). They're very different styles, yet they complement each other nicely.
reply