Splitting implementation and interface in C++
Splitting implementation and interface in C++
Posted Feb 25, 2025 1:27 UTC (Tue) by ras (subscriber, #33059)In reply to: Splitting implementation and interface in C++ by mb
Parent article: Rewriting essential Linux packages in Rust
cargo doc
I had said earlier I wanted the ability to say to the compiler "not here, at this boundary conventional linker is all you need". I also said C++ gives you the ability to do that, by splitting stuff into .h and .cpp. @franz said "but that's the old way, boost for example doesn't do that". That's true, but the point is the people who wrote boost made the decision to adopt the way Rust does it. C++'s std makes a different decision. @taladar said C++ compiles are slow. I'm guessing that's because the packages / libraries he is working adopt this newfangled way, and everything gets recompiled all the time. He's blaming C++ for that, but I'd argue that fault lies at least as much with the package authors for making that choice.
@franz then said "oh but it's hard to think about what has to be monomorphized and what isn't, and besides redeclaring everything in .h is verbose and a lot of work". I don't have much sympathy for the first part - I did it all the time when I wrote C++. The second is true, the information in .h is redundant. A modern language shouldn't make you type the same thing twice without good reason.
Those language differences were swirling around in my head when I wrote: "Or perhaps the required information gets folded into the .o". It was a thought bubble. But it's key point illustrated the idea nicely with your "cargo doc" comment. Rust could add something to the language that says "this source is to be exported (made available) to people who want to link against my pre-compiled library", in the same way "cargo doc" exports stuff. That information would be roughly equivalent to what's put in a .h file now. But where would you put it? The thought bubble was place it a section of the .elf object that holds the compiled code. Call it say a ".h" section. Then when someone wants to compile against your library, they give that .o / .so / .a to both the compile phase (which looks for the equivalent of the .h sections) and the link phase (which just wants the compiled code for the non-monomorphized stuff, which - if the programmer has done their job - should be the bulk of it).
The ultimate goal is to allow the programmer to decide what needs to be monomorphized, and what can be pre-compiled. And to have Rust tell the programmer when they've mucked that boundary up. I guess it would get an error message like: "This type / function / macro has to be exported to the .h, because it depends on type T the caller is passing in". Right now Rust programmers don't have that option, and that leads to the trade-offs I mentioned.
