4 ms·
I agree that the email does not set out a solid argument and the "angry technical shlock" is unclear - that's what I was getting at by "aggressively worded". I
by geofft 5y ago
I agree that the email does not set out a solid argument and the "angry technical shlock" is unclear - that's what I was getting at by "aggressively worded".
I think there exists a solid and reasonable argument along these lines, even if Linus did not phrase it, though, which is what I'm getting at. (And I suspect Linus and others do believe that argument, even if they're not good at communicating it, so it would be worth engaging it.)
It's true that you could write C++ without exception handling, and that you get to choose the abstractions. But it matters what the language makes easy for you - what is idiomatic and conventional in the language - what programmers who use that language have come to expect their libraries to do.
Otherwise you could just say that we can solve our string problems in C without switching languages by simply using length-prefixed strings and buffers and not relying on NUL termination. After all, the kernel doesn't use a userspace standard library, and even in userspace there are string-handling libraries in C that avoid those problems (e.g., http://bstring.sourceforge.net/ http://bstring.sourceforge.net/ or https://github.com/antirez/sds https://github.com/antirez/sds).
But the fact remains that the kernel has its own implementations of strcat and sprintf nonetheless (in the lib/ directory), simply because C programmers expect them. And people call NUL-terminated strings "C strings" and length-prefixed strings "Pascal strings," even though you can do length-prefixed strings just in C and NUL-terminated strings just fine in Pascal.
It is very hard to write abstractions in C++ that work the way the abstractions in Rust work and are still natural and easy to use. No one is disputing they're possible to write. But if you look at the blog post I linked, it demonstrates that even with the benefit of decades of experience in C++ and knowledge of what Rust is doing, and even with additional language features, and even with the input of people who did in fact choose their abstractions (std::string_view was based on libraries local to Google, LLVM, and Bloomberg), you still do not get the level of safety that the equivalent Rust feature has.
Concretely,
1. C++ has language-level features (most importantly, try/catch/finally) that make error handling via exceptions easy and natural to use. Rust has language-level features (most importantly, pattern matching on sum types) that make error handling via a result type easy and natural to use. You can of course choose not to use exceptions, but you're giving up a good language feature and you don't have language support for the thing you want to do instead. This is much like saying "Don't like NUL-terminated strings? Don't use them" - technically true, but have fun avoiding string literals.
2. This is not a general-purpose tirade against abstraction. It is a very targeted tirade against implicit conversions to heap-allocated types, which are natural and idiomatic in C++. See my comment https://news.ycombinator.com/item?id=27749301 https://news.ycombinator.com/item?id=27749301 elsewhere in this thread for details on exactly what Linus is objecting to and why it is entirely reasonable for a kernel to object to it. Again, you could certainly decide to avoid implicit allocations in your nonstandard library, but it is not idiomatic C++.
3. C++'s object model is legitimately quite complex, involving things like multiple inheritance, partially-constructed objects during the constructor, and phrases like "morally virtual base". It works - there's definitely a lot of working C++ in the world - but that level of complexity isn't needed by the kernel, and (certainly in my personal experience) it's easy to get into deeply confusing situations. The kernel also cares about things like fitting objects into cachelines. The kernel doesn't need multiple inheritance with the ability to have different offsets into an object depending on what pointer type you're using, but it does need the ability to specify that objects implement multiple interfaces that carry no data (what the kernel calls "operations structures," and what Rust calls "traits"). In C++, the only mechanism the language gives you for this is multiple inheritance. There are use cases where full multiple inheritance is useful, and C++ is preferable to Rust for such cases. The Linux kernel is not one of them.
It's true that you can do all the things Rust can do in C++ if you try hard enough. But I'd reiterate the point which you made clearly, that human beings simply do not seem able to write flawless C in practice, even though it is theoretically possible, because it is simply not idiomatic. From the blog post I linked, it seems like there's no reason to expect that asking humans to write flawless and unidiomatic C++ will work out any better.