And more importantly, a place where C++ has failed.
And more importantly, a place where C++ has failed.
I wouldn't say Windows drivers were C++, more like "C with Classes" (and maybe a little bit C++)
Also, Windows drivers are usually much more convoluted than Linux drivers https://github.com/microsoft/Windows-driver-samples
Anything that uses 1% of C++ specific features is C++.
C as used by Linux kernel is not ISO C, yet no one argues it isn't C.
Technically correct and this raises an important point. How long would the kernel take to compile in C++ versus in C?
Though in practice if you think the kernel should support all the craziness of "modern" C++ you're going to have a big problem in making it work in kernel space. (things like heavy templates, etc)
And just to disagree with Linus I think the 'new' part is the least of our problems in making the kernel work with C++
As for compilation times, it is all a matter of how the build system is architected.
Most of my C++ build pipelines compile faster than Rust, in make clean all scenarios.
There is no black magic in how to achieve it, just knowing what one is doing, making use of binary libraries, build caches, and be aware of how much craziness is allowed, most of which wouldn't be allowed on the kernel anyway.
How to do this is surprisingly easy: write small crates that do 1 thing.
A Rust crate is a C++ TU, pretty much everybody that complains that Rust is slow is comparing Rust crates that are 10-100x larger than the size of the .cpp files they use in their C++ projects.
For some reason, people, and particularly C++ devs, like to write huge Rust crates (I’ve seen crates with 200k LOC). They’d never write a 200k LOC .cpp file, so I really don’t get why they do this.
Either way, if your Rust compile times are slow, it’s probably your and only your fault.
How to get blazing fast Rust compile times isn’t a secret.
We have a lot of data of the time it took to compile C++ TUs vs the equivalent Rust crate, and Rust reliably compiles at least one order of magnitude faster, sometimes two.
All our crates are less than 10kLOC, median is 1812 LOC.
Rust crate binary compatibility is also pretty awesome, so that in general a new hire does not need to compile anything at all (an unmodified build just fetches binary objects from sccache). When they start modifying the project, only those binary objects that actually changed have to be recompiled, the rest is just dynamically linked. This gives you <5s edit-compile cycle if you are working on a leaf crate.
If your C++ compile times are slow, it’s probably your and only your fault.
It also supports unity builds, and C++ modules TS, so we can compare the performance of all 3 approaches
Rust consistently beats all of them by at least an order of magnitude in apples to apples comparisons, with at least an order of magnitude less fiddling in terms of modifying the source code, fiddling with build system and compiler options, etc.
So in our experience, Rust gives you 10x faster compile times with 10x less effort than C++ if you use similarly sized compilation units.
Rust makes it easy to write compilation units that are 10x-100x faster than C++ ones and still compile reasonably fast for their size, but we don’t allow this by convention (we have a clippy lint that rejects crates that are too large).
So do you also have something comparable to Incredibuild and Clearcase derived object files in place?
We supported them for a while (until 2015 or so; no perf difference over 3 years), but Clang modules gave the exact same perf and was easier to use, and unity builds were much faster for clean builds anyways, so there was no point in maintaining PCH in parallel to these two other options.
For people that are not aware, PCH, modules, etc. bought us 20-40% compile time reduction. I expect 30% or so to be typical for projects that are using TUs correctly.
A 30% compile time reduction or even a 2x compile time reduction is not “life changing”. It’s peanuts.
Particularly compared with the 10-100x compile time reductions that Rust gives you. From 100min to 1 min, that’s a huge difference. From 100 min to 50-70min? That’s an irrelevant difference.
If you think a simple tautology disproves someone's claim, there's a good chance you're missing the point.
One can endlessly discuss pros and cons of using C++ in the Kernel, but in the end it's about either making a bet on C++ or not; it's not that one can or has to formally prove that the bet is right to avoid being called religious. Linus listed cons that he finds important (which are real) and decided not to bet on C++.
Linus has a long history of successfully managing Kernel, I think this makes his bets worth something. So yes, passing Linus filter is a great success for Rust, and even greater given that C++ didn't pass.
Also, the fact that C++ succeeded somewhere doesn't disprove this claim.
> You'd have to get rid of some of the complete garbage from C++ for it to be usable.
> One of the trivial ones is "new" - not only is it a horribly stupid namespace violation, but it depends on exception handling that isn't viable for the kernel, so it's a namespace violation that has no upsides, only downsides.
> Could we fix it with some kind of "-Dnew=New" trickery? Yes, but considering all the other issues, it's just not worth the pain. C++ is simply not a good language. It doesn't fix any of the fundamental issues in C (ie no actual safety), and instead it introduces a lot of new problems due to bad designs.
[1] https://lore.kernel.org/ksummit/CAHk-=wiwZWAo_Ki587FD2BrAQVK...
Linus didn't like the panic-on-fail memory allocation handling from userspace Rust for the same reason and so the kernel Rust people had to be very clear early on that was temporary and wouldn't survive from a prototype into the actual kernel.
There are some edge cases where this is surprising and I expect Linus will ask for further work. e.g.
let x = "Clowns".to_owned();
let mut y = x + " are not welcome here";
y += " any more.";
The first variable ends up with a growable String, containing the word "Clowns" so that's an allocation and it might fail, in userspace it panics, in kernel Rust should this not compile? If it needs to "return an error" how?The second line needs a concatenation operation, it won't end up with a new String, the old one gets moved instead (in Rust this means if you try to refer to x after this that won't compile, it was moved and the compiler knows that) but it also gets a concatenation to add this extra string to the end, probably allocating again. Thus it too might fail, in userspace that would panic, but in the kernel what happens this time? Does this Add operator have a Result type now?
The third line concatenates again, but this time the operation doesn't even have a return type, it's obliged to mutate y, so there is just no possible way it can result in an error. Either this mustn't happen in the Linux kernel, or it panics when it can't allocate.
Whereas these are all fine, they don't allocate and aren't writeable so they won't bother Linus:
let x = "Clowns"; // A str, definitely UTF-8 encoded
let y = b"\xf7\xf7\xf7"; // not Unicode, just some bytes
let z = r#"No need to "escape" the double quotes now."#;> Does this Add operator have a Result type now?
Essentially yes. There could be other APIs which accomplish the same results but have different constraints, such as not being able to mutate strings without also creating a Result to be handled. Yeah you lose some ergonomics but even that feels more ergonomic than C.
I think Rust is in a unique position in that zero / extremely few panics is actually achievable, and easier to achieve, with less manual discipline, than C.
Maybe you understood this as purely a rhetorical question, rather than I'm lazy and didn't take time to go read the source code yet when I wrote it. I apologise.
impl Add<&str> for String {
type Output = String;
#[inline]
fn add(mut self, other: &str) -> String {
self.push_str(other);
self
}
}
So, the implementation of Add for String in the kernel when it exists gives a String, it does not give a Result.What is unclear to me still is the significance of no_global_oom_handling. If this is the intended final state of Rust for Linux, then in fact all these traits just go away, so, you can't concatenate Strings with the + operator, and in fact most ways to do stuff with Strings in Rust go away too†. Which is one way to approach it, it's not as though people were dying to do string manipulation inside the Linux kernel anyway.
† In Rust the String type is a growable heap allocated data structure you can change while str (mostly seen as the reference &str) is just a "slice", a length, plus a memory location, plus a promise that the series of bytes in the slice form valid UTF-8 encoded Unicode, but with no way to grow or change it.
[1] https://doc.rust-lang.org/std/string/struct.String.html
[2] https://doc.rust-lang.org/core/str/index.html
[3] https://docs.rust-embedded.org/book/intro/no-std.html
[4] https://github.com/rust-lang/rfcs/blob/master/text/2116-allo...
The percentage of c++ programmers using exceptions seems to increase in jetbrains survey - for the last one it's at 80%, it used to be close to 60% iirc. I wouldn't call 20% a big fraction.
There are no other ways to prevent an invalid object representation to exist unless one is willing to wrap literally everything in optionals, basically reimplementing an exception unwind stack by hand.
I suspect the answer is: those traits not implemented for String in no-panic mode. And instead you have to use fallible “try_extend” methods that return a result?
I’m just guessing though.
The reason you can use String with the + and += operators is because it implements the Add and AddAssign traits[2], and it looks like the implementations are already feature gated for OOM handling so might not be implemented for String.
[0] https://github.com/rust-lang/rust/blob/master/library/alloc/...
[1] Note that the compiler knows about it to provide better diagnostics to the user, but this has no impact on the language itself.
[2] https://github.com/rust-lang/rust/blob/master/library/alloc/...
Oh, and good point about the existing feature gate; I debated going and looking to see if one existed yet or not, but I was hungry so I didn’t. Glad you were around to point it out.
Because this is all just library features from a library that isn't being used. They could use core::alloc and have panic on allocation failure, or they could implement their own library and that library presumably wouldn't have the explicit panics that core::alloc has.
Kernel rust isn't a matter of a variation of the language, it's just about not using the standard libraries
Some might argue this doesn't qualify as "C++ trying", but I think it does.
Just the superior type checking, being able to use RAII and simple templates to replace macros completely transform the way you work on a fundamental level without doing anything fancy.
Although it's not as pleasant as it should be given its historical baggage (the bad rap is sadly well deserved), I still consider it to be miles ahead of C for embedded work to the point that while I do like Rust a lot, I still consider it to be overhyped in this area.
Another good thing to look at is how disgusting std::move is and how straight forward the equivalent is in Rust. Additionally, there isn't anything to prevent you from using some variable where you used std::move (which invalidates the contents of said variable). Rust will not allow you to use some variable if ownership has moved.
Finally, I personally despise that you end up needing so many constructors in C++ just to do basic things like the assignment operator.
However I also think how we got to the modern C++ standard, and how early we are with Rust.
I think of all the weird C++ features I had to use over time in embedded, and I'm glad I always had the option to have manual control over memory sizing, the ability to alias pointers and everything that you normally wouldn't want or consider unsafe.
I also think of Rust and this hell-bent approach to memory safety, and actually think that on embedded I basically never have dynamic memory allocation, and very rarely have issues with ownership that I could solve nicely (without fighting with the very limited lifetime constraints we can have) with Rust. On small targets there is no memory allocation. There's often no copying at all, because it's too expensive: ids and counters are often exchanged over shared buffers.
What I feel is a massive improvement in terms of safety in embedded are state machine compilers more than memory safety. Think "ragel", or similar compilers. Using these in embedded couple with wither C or C++ is a game-changer.
Memory safety and pointer lifetimes start to be an issue with much bigger targets. I don't have experience with kernel development, but if you can run linux on a platform I would hardly call it "embedded". Or at the very least, we're talking about the very high-end embedded platforms.
I think Linus was wise not to empower people to write shaky abstractions in one of the world's a) largest and b) most important low-level codebases.
Most MCUs compilers had little if any support for most C++ "fanciness", which meant that trying out C++ ended almost always in some big catastrophe, either by hidden bugs being inserted by those compilers, or portability disasters (+80% your supposedly portable code uses this feature that compiler X doesn't have).
However times have changed. The features and C++ general coding style itself is quite different that what you'd write in '98.
If you're considering Rust, you should consider the modern available C++ with all the latest tooling to be fair. And it's _pretty_ good, all things considered.
At least with C++ most corporations actually use binary libraries, we aren't doing Gentoo style across our codebases.
I don't expect people to start (re)writing their drivers in Rust anytime soon. Maybe some PoC things and stray FAANG-devs aiming for promotions, but it is highly unlikely I will need to have those drivers enabled. Hope Rust compilation times improve by the time it becomes mainstream in systems.
> At least with C++ most corporations actually use binary libraries, we aren't doing Gentoo style across our codebases.
This is not relevant for the kernel development (Linus' case) and doesn't match my experience working with C++ code bases for ~10 years.
There are other platforms that have embraced C++, both of them run on my pocket.
So while I enjoy doing toy applications in Rust, C++ is the language I have available on the IDE installer to write native code on our projects.
Until Rust manages to get a similar checkbox on the same installer, it isn't going to be an option for certain kinds of corporations.
Since then, C++ has gotten massively better. We now have strings and vectors as part of the standard library, so there's no need to use someone's quirky implementation.
As I said in a previous post, C++ gets you most of the way to Python, so there's really no point in not using it.
Still, it's taken us until C++17 to get sum-types (via variants), and it is amazing to think that it took so long to get such a basic idea included. The handling of variants could do with a bit of simplification, mind. And I'd do wish they could implement the equivalent of "typeclasses", enabling recursive data structures.
I'm a big C++ fan, in case that wasn't clear.
It's share of developers has been growing since early 2013, when C++11 began to be widely available, and more recent revisions have apparently boosted its growth.
For example, Click modular router for Linux kernel is written in C++: https://github.com/pbuonado/click/blob/master/linuxmodule/mo...