I've said this before on here and I'll say it again. The QEMU code base is a nightmare. The amount of fake C++ is mind numbing. Every time I come across a variable or strict declaration or method with the word "class" in it, I'm reminded of how much easier the whole thing would've been with C++. You can't even compile C++ into QEMU because of how the headers use keywords. That's not even touching their macro abuse template functions. You know what has templates and classes? C++. And constructors. There's just so much.
All this to say: if Rust can make it in, that's great, because I'm tired of dealing with C's simplicity for more complicated tasks. I'm not a rust user but if it lets me use classes and templates, I'll switch over
> I'm not a rust user but if it lets me use classes and templates, I'll switch over
Yer not switching any time soon then. Rust does have methods but not classes (QOM’s inheritance is specifically called as an issue in TFA) and it uses Haskell-style generics rather than C++-style templates.
I mean it has polymorphism via v-tables and composition via traits, that's enough object-orientation for me. Inheritance is a core principle of OOP but in practice most C++ class hierarchies are relatively flat (there are exceptions like Qt, which I think uses inheritance in a good way), in practice most of that can be mimicked with embedding and composition well enough to work. Not sure if you want to call that object-oriented but operating on polymorphous structures that encapsulate their data is pretty close to object orientation (the good parts of OOP, at least). And I'm not a big expert on Rust but I think you can do most of the things we do in C++ with templates using macros.
The one lagging thing that isn't easy with rust's generics is expressions, and that looks to be getting kicked down the road indefinitely. You can't have Foo<N> * Foo<M> -> Foo<N+M> or similar. That is a useful thing for statically sized math libraries and for performance oriented metaprogramming. The latter can be clumsily handled with macros, but not the former. It is also blocking portable simd from stabilizing, apparently now indefinitely. I still wouldn't pick any other language a new production product, but I find the stalling out on const generic expressions frustrating, as simd and static math libraries are things I use daily.
You misunderstood.
N, M are supposed to be integers (const generics); in your example code you've made them types.
Also, your `type Output = Foo<<N as Add<M>>::Output>;` just means "multiplication has the same return type as addition". But desired is that multiplying a Foo<4> with a Foo<3> results in a Foo<7>.
Rust decided that it's important to not have instantiation-time compiler errors, but this makes computation with const generics complicated: you're not allowed to write Foo<{N+M}> because N+M might overflow, even if it never actually overflows for the generic arguments used in the program.
aiui this isn't inherently limited by instantiation-time error messages and is available on nightly today with the generic_const_exprs feature. It's in the pipeline.
I could go on and on about the limitations of min_const_generics, missing TAIT (type alias impl trait), unstable coroutines/generators, etc. but none of that stuff erases how much of a miracle Rust is as a language. The industry really needed this. It's practically the poster child of memory safety and security-critical industries are picking it up and Ferrocene is pushing it in safety-critical environments as well and it's just. Good. Please. Finally a serious contender against the terrible reign of C and C++.
> I didn't like the way Simula I or Simula 67 did inheritance (though I thought Nygaard and Dahl were just tremendous thinkers and designers). So I decided to leave out inheritance as a built-in feature until I understood it better.
> OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP.
Fun that this was mentioned too:
>> it uses Haskell-style generics
> polymorphism via v-tables and composition via traits, that's enough object-orientation for me
For which Alan Kay had this to say:
> My math background made me realize that each object could have several algebras associated with it, and there could be families of these, and that these would be very very useful. The term "polymorphism" was imposed much later (I think by Peter Wegner) and it isn't quite valid, since it really comes from the nomenclature of functions, and I wanted quite a bit more than functions. I made up a term "genericity" for dealing with generic behaviors in a quasi-algebraic form.
In a way it's fascinating to see how C++ has shaped (dare I say warped) the collective vision of how to do OOP.
For example, as a Rubyist, which was heavily influenced by Smalltalk, it is fascinating how hard it can be to explain how fundamentally different message vs call are, and what's the whole point of modules, mostly because the other viewpoint is so warped as to cognitively reject that it can be any different from the C++ model.
You can do it with macros, the problem is
A) documentation
B) macro abuse
It's so much harder to debug things when function calls are secretly macros, and if I didn't have the vscode cpp language server for goto definition, I'd be completely lost. I'd wager that only 5% of the #defines aren't auto generated by occasionally recursive macros. Maybe hyperbole. Makes it really hard to figure out how existing code works.
:( the QOM inheritance is where I've had my wrist bugs. Recently I merged from master (upgrading me from version 8.something to 9.2.?) and they dramatically changed the resets. I tried the new way and had segfaults in bad places that went away when I reordered code that shouldn't have ordering requirements. That was too scary so I switched to their legacy reset function and it was all fine again. All this blind casting of opaques makes me nervous.
I don't think he literally means templates and classes. Rust has equivalents that do the things you want templates and classes for (generics and structs/traits respectively).
I completely agree with his point about reimplementing C++ badly in C. GNOME does this too in their libraries. He will be much happier with Rust.
Hmmm I think I actually really want classes. Something that combines data and methods together without more function pointers. Template vs generic I don't care about
Rust structs are in some ways similar to C++ classes. You can combine data and methods together with them without worrying about things like function pointers.
C++23 is a godawful mess; especially the functional paradigms (which look beautiful in e.g. OCaml) that got shoehorned kicking and screaming into the morass that C++ had already been.
If you read function specs in the OCaml docs, they're understandable and the syntax is clean; the same concepts bolted onto C++ look like line noise, both in actual syntax and the (allegedy) English-language description on en.cppreference.com.
Reading the C++ standard itself is an exercise in futility, very much unlike the C standard. (Although, latest developments in C land are repulsive too, IMO.)
The C++ committee's tantrums and scandals (physical violence at meetings!) put the worst that has been seen in open-source communities to shame. <https://izzys.casa/2024/11/on-safe-cxx/> has been posted to reddit and HN recently.
C++ compilation still takes absolutely forever, and C++ template error messages are as incomprehensible as ever.
One important problem with C++ (repeated ad nauseam, so this is nothing new) is the unforeseen and unintended harmful interactions between such language features that were supposed to be orthogonal. The only remedy for that is to stop adding stuff to the language; but nooo, it just keeps growing.
Another important problem is that, the more the compiler does for you implicitly, the less you see (and the less you can debug) what happens in the code. This is not a problem in OCaml, which is a managed language with a safe runtime, but it's a huge problem in C++, which remains a fundamentally unsafe language. (And yes, once you start extending OCaml with C, i.e., mixing implicit and explicit, OCaml too becomes unsafe, and a lot of head-scratching can occur.) A home-grown object system in C is at least explicit, so it's all there for the developer to read, instrument, step through, and so on.
I cant believe I just read that entire izzys.case post. Wow. I couldn’t possibly assess it all for accuracy, but if that’s reasonably correct, just wow.
What are the advantages of trying to use fake c++ instead of actual c++ for their use case? I'm sure there are / were smart people working on the project. Do they keep decision records? Have you had a conversation with the relevant people about it?
> What are the advantages of trying to use fake c++ instead of actual c++ for their use case?
There are exactly none, but there was a time during the mid- to late-90s when it was hip to implement OOP frameworks on top of C (C++ only really became a realistic option once C++98 was widely supported which took a couple of years, ...also there was such an extreme OOP hype in the 90s that it is hard to believe today - EVERYTHING had to be OOP, no matter if it made sense or not).
QEMU seems to be from around the end of that era (first release apparently in 2003), and looking at the code it looks exactly as I imagined, with meta-class pointers, constructor- and destructor-functions etc... it would almost certainly be better to use a simple non-OOP C-style, but that was an 'outdated' point of view at that time (since everything had to use that new shiny OOP paradigm).
The object model in QEMU is from 2007-2011. Internally there isn't a lot of inheritance (it's pretty shallow), but yeah I guess "is-a" is what was in vogue at the time.
However there is a reason to have the object model, and it was added because some things were getting out of hand. You had to write three parsers for everything you added: one for command line, one for the text-based command interface and one for the JSON API. And you had to do it once for each kind of "object" (device, network backend, disk backend, character device backend etc.). The object model is designed to let you write code just once for all three and to reuse the interface across object types.
C++, especially before C++11, was a total mess. Even today, it's super easy to shoot yourself in the foot and you literally can't learn the entirety of the language due to how massive it is. This still doesn't justify doing the absurdity of macro magic and garbage I've seen people pumping out in C over the years though.
IMHO if you deliberately use C it's because you want to keep things simple. Sometimes C++ will inevitably lead to overcomplicated designs nobody needs; there's a reason why 90's C++ feels massively outdated, while 90's C is still (mostly) accessible.
C is a great way to keep the urge people have to shovel in OOP even when it doesn't really make sense. When I see some bullshit C++ code at work, I often think, "if people had to write this in C, they wouldn't have overthought this this much"...
My 2 cents is that there was a Java craze back in the '90s that basically infected all code designed in that time period, and that's how we got some very weird nonsense that in hindsight was poorly thought out. Just look at stuff like GTK+ and GObject...
The GObject system for all it's faults serves a purpose. Similar to COM in Windows it allows mapping of higher level languages to libraries. Without it there wouldn't be all the bindings we have to Python, JavaScript, Vala and Rust today. I wouldn't say it was poorly thought out so much as mismatched with it's user's typical uses and expectations.
Yeah but it made literally zero sense to write GObject in C. They've reimplemented inheritance and the like with macro BS while the GNU project literally had both a C++ and Objective-C compiler - Objective-C warts and all was a perfect fit for GObject.
It makes sense if you consider that it was created for making c libraries bindable to higher level languages. That's more a flaw in it's design rather than implementation. If they were to design such a system now I'd hope it would be designed as a language independent ABI, Runtime and IDL like the Wasm Component Model.
I have been guilty of this if only in school. A class assignment could be completed in java or c and I knew c++ and c and my teammates knew c. I really wanted to do oop because I was ignorant, but none of us knew java so we did OO in c. It was horrible. I like the simplicity of c. I wish there was a zig that was immutable by default, and had a borrowchecker, but I think that road inevitably leads to a GC jitted language or to something as complicated as rust. Well almost as complicated, at least macros would still be written in the same syntax as normal runtime code.
I worked for a large infra project that was mostly written in C with the kind of trickery that is being here ascribed to QEMU code (I trust the parent, but I haven't seen the code myself).
It's a common thing to do in projects like this in C. While C++ solves some of the problems for larger projects, it brings so many more, it's usually not worth it. Projects of this kind, essentially, invent their own language, with their own conventions. C is just a convenient tool (given the alternatives...) to do that, as it's treated as a kind of a transpilation target. Think about languages like Haskell which essentially work like that, but went even further.
So, what typically happens is that memory allocation, I/O and concurrency need to be done differently from how the language runtime wants to do that. So, at this point, you basically throw away most of the existing language runtime. From my recent encounters with different bugs in QEMU, I imagine that they really, really need a lot of customization in how memory is allocated (my particular case was that QEMU was segfaulting when running ldconfig in aarch64 emulation, which turned out to be due to ldconfig having some weird custom-made memory allocation pattern which QEMU cannot account for.) It's easier to fight this battle with C than it is with C++.
You can define your own "language", the way you describe it, in C++ more easily than you can in C. C++ gives you more powerful and expressive tools for doing so. A program is not improved by foregoing these tools. There's nothing in C++ forcing you to do anything that makes anything "harder".
I wouldn't be so quick to put "easy" and "C++" in the same sentence... Also, C and C++ language tools suck, when it comes to making a language compared to anything in Lisp family, for example, or any language that has Lisp-like macros. C wasn't chosen for it's ability to make other languages. It was an easy target. The benefits are the simpler and more malleable runtime that can be made to do all sorts of weird things where C++ would be much harder to deal with.
In other words, if you don't want the language runtime, if you don't want C++ templates, smart pointers, classes, lambdas, exceptions etc. But, you want to be able to manage memory for eg. realtime kind of system... simply removing all the unwanted stuff from C++ is harder than it is with C.
And, if you did want some / all of those features of C++, there are saner languages to have that (and you'd pay for it by having to drag in their runtime and conventions). Before Rust was a thing, I've seen people choose D instead, for example, as a kind of middle ground between the asceticism of C and unnecessary excess of C++.
Lisp is a non sequitur. C++ can do the same "weird things" C can. There is literally no advantage whatsoever of choosing to wear the C hairshirt instead of using C++. Not a single one.
Sure, Rust is better than C++ in some ways. You can have a legitimate debate about C++ versus Rust. There can be no debate about C versus C++: the latter is better in every single way.
> unnecessary excess of C++.
Is the unnecessary excess of C++ in the room with us right now?
Again, you don't have to use any part of C++ you don't like. A few minor spelling differences aside, it remains a superset of C. Using C++ not C will not hurt you in any project whatsoever.
I don't know... I basically already answered your question: C++ has way too many things to remove. People working on projects like QEMU don't want exceptions, smart pointers, classes, virtual methods, templates, lambas etc. So, it's more effort to remove all of that / make sure your code doesn't accidentally use any of that.
C is simpler because you don't need to remove any of that stuff, because it's not there to begin with.
My argument about Lisp doesn't say that anyone should use Lisp instead, it just points out that people making the choice between C and C++ aren't concerned by how easy it is to build new languages using the language building tools of C or C++ because both are very bad at it. Had the objective of building a new language been the primary objective of such projects, they'd be using a language with superior language building tools (not necessarily Lisp, there are many alternatives, it's just that neither C nor C++ are good at it).
C++ makes everything harder by tempting you with all the unnecessary tools. And some of the mental load only goes away if you can be 100% sure that nobody in the project uses the feature.
I was going to post the same thing. Others on this thread may not have had experience with very large C codebases and hence haven't seen this play out. To a large degree C++ is just capturing what was already widespread practices in C, and indeed assembly before that.
The only advantage I know of is that it means you stay within the C ABI. It can simplify linking a little in some cases, or FFI. I guess compilation is faster too.
Yeah, even integrating any C++ into it and calling any QEMU functions via extern "C" declarations when including the headers cause many issues because many variables are named "new" or have type cast issues.
It's a full time job on its own to fix all of the compilation errors. Could probably fix it with coccinelle scripts to make some parts easier, but still, validating the codebase with different compilers to make sure there's no resulting subtle breakage either still requires a lot of effort.
All this to say: if Rust can make it in, that's great, because I'm tired of dealing with C's simplicity for more complicated tasks. I'm not a rust user but if it lets me use classes and templates, I'll switch over