14 ms·
Header files are one of the things I miss the most about languages that aren't C. Having a very clear distinction between public and private, and interface and
by TheNewAndy 2y ago
Header files are one of the things I miss the most about languages that aren't C. Having a very clear distinction between public and private, and interface and implementation is one of my favourite things about C code (at least the way I write it).
Being able to just read through a library's .h files to know how to use it is really nice. Typically, my .h files don't really look like my .c files because all the documentation for how to use the thing lives in the .h file (and isn't duplicated in the .c file). It would be entirely possible to put this documentation into the .c file, but it makes reading the interface much less pleasant for someone using it.
- legobmw99 2y agoSome other languages have equivalents (OCaml comes to mind), but usually they’re less necessary
- Lvl999Noob 2y agoI don't really program in C much so please correct me if I am wrong. There is a flaw in header files in that they work the exact same for dynamic vs static linking, right? If I am making a library in C for static linking, I need to put my internal details in the header file if I want the user's compiler to be able to use those details. But putting them in the header files also means they are part of the public interface now and should no longer be changed. Basically, I cannot do something like a struct with an opaque internal structure but a compile time known layout so that the compiler can optimise it properly but the user cannot mess with the internals (in language supported direct ways).
- TheNewAndy 2y agoThat is less about header files, and more about how machine code works. If you want to have some abstract type where you don't let people know anything about the innards, but you do have an explicit interface which enumerates what you can do with it, then yes - you can only really pass around pointers to these things and people outside your abstraction can only pass references not values. If you want people to be able to pass your abstract type by value (among other things), then either you need to let them know how big the thing is (an implementation detail) or you have to expose the copy implementation in such a way that it could be inlined (more implementation details). Sometimes, the "pure abstraction" approach is best where you only ever deal with pointers to things, and other times the "let's pretend that people do the right thing" approach is best. I don't see this as a header file thing though.
- Lvl999Noob 2y agoI disagree with you on this. In another language with explicit public / private separation, the compiler can have access to the internal layout of a type (and thus optimise on it) without letting the developer mess around with it directly. I am assuming static compilation of course. Across a dynamic boundary, I would expect this compiler to behave like a normal C compiler and not use that layout. In a header file, the information for the compiler and the user are the exact same which means you can't reduce your public interface without straight up hiding more of yourself.
- TheNewAndy 2y agoPersonally, I'm happy to just let a Sufficiently Advanced Compiler do link time optimizations to deal with that level of optimization and either take the hit, or make more things public while that compiler doesn't exist. Let the header files be written for people to read first, and only if there is actually a big performance issue, and the problem is the interface do you need to revisit it (and I'm not just saying this - I will frequently and happily go back and modify interfaces to allow for less data movement etc, but most of the time it really isn't important). I think you are probably right to disagree with me though - I think I should have said that it is more of a limitation on how object files work, rather than how machines work. Object files aren't the only way things can work.
- uecker 2y agoA compiler can still have access to the internal layout in C via link-time optimization.
- johannes1234321 2y agoLinker is something different from Compiler (even if often called via same Frontend)
- uecker 2y agoThe linker invokes the compiler again.
- lzsiga 2y agoThe proper way is not exporting implementation details at all, instead define opaque types in your header files like this: `typedef struct ssl_st SSL;`. This comes from OpenSSL, it means users can use `SSL *` pointers, but they don't know what those pointers point to. Of course you can also have internal header-files within your own project, which you don't share with the end-users of your product.
- uecker 2y agoIn C programs only the external definition of an interface goes into the header of a library but not implementation details (there could be headers intentionally exposing details for internal use, of course). The problem is real for C++. Optimizers can look across translation units nowadays (link-time optimization), so there is no reason to expose internal details in a header for this. For dynamic libraries this does not work of course, but it also shouldn't.
- johannes1234321 2y agoEven for C it's normal ways true: When size of structures have to be known to the user (if they are supposed to keep them on stack or mallox themselves or whatever) the "private" structure often ends up in a "private" header. (There are ways to still hide it, but they cause work to keep things proper) And then there are cases where (often die to performance) you want inlining of some operations without relying on Link time optimisation, then implementation has to go to headers, too.
- kouteiheika 2y ago> Header files are one of the things I miss the most about languages that aren't C. Having a very clear distinction between public and private, and interface and implementation is one of my favourite things about C code (at least the way I write it). I always found this argument baffling, because the way some other language solve this problem is with tooling, which is a much better way to do it in my opinion. Take Rust for example. You want to see the interface of a given library and see how to use it? Easy. Type in `cargo doc --open` and you're done. You get a nice interface with fully searchable API interface with the whole public API, and it's all automatic, and you don't have to manually maintain it nor have to duplicate code between your header and your source file.
- panic 2y agoAs someone who likes C header files, I enjoy manually maintaining them. Designing the interface separately from the implementation feels good to me, and a well-structured .h file is nicer to read than any auto-generated docs I've encountered.
- chii 2y ago> Designing the interface separately from the implementation feels good to me would you make the same argument for java then?
- otteromkram 2y agoAbsolutely not.
- tpoacher 2y agoyou don't like java interfaces?
- marginalia_nu 2y agoEven as a java programmer, I think this is a bad take. Java doesn't force separation of implementation and interface, and java interfaces also have a lot of weird stuff going on with them. Java also has too many tools for this. You both have class/interface, but also public/private. I honestly think C does it better than Java.
- kevin_thibedeau 2y agoHeader files are really a weak hack to deal with resource constrained platforms from the 70s. They only work if you stick to a convention and pale in comparison to languages like Ada with well architected specification for interfaces and implementation without ever needing to reparse over and over again. I do enjoy using C but that is one area where it should have been better designed.
- billfruit 2y agoThey are also somewhat of hassle and something not necessary to have.
- ryukoposting 2y agoHeader files also make it a lot more obvious how you're supposed to distribute a library as a binary, which is good.
- harisund1990 2y agoI love headers but I wish you could split them in two so that private functions and variables can line in the c file. This would help reduce a lot of header bloat as well.
- lzsiga 2y agoIt is perfectly valid to use more than one header files: some of them can be public (meant to be seen by users of your library), others can be private or internal (only used by your own sources).
- chikere232 2y agoAlso, usually it's pretty rare to have things internal to one C file that need explicit prototypes. It's easier to just put things in the right order so the funtion definition etc is before its use.
- m463 2y agoI agree with you, but I don't. The way C handles header files is sort of "seems-to-work" by just blindly including the text inline. I know this is not a much-used language, but in comparison, Ada did a pretty nice thing. They have the concept of packages and package bodies. The package is equivalent to the header file, and the package body is the implementation of the package. I remember (long ago when I used ada) that everyone could compile against the package without having the package body implementation ready so the interfaces could all work before the implementation was ready. an in another direction, I like how python does "header files" with "import". It maps easily to the filesystem without having to deal with files and the C include file semantics.
- fuzztester 2y agoObject Pascal (not the original Pascal) versions like Delphi and Free Pascal have syntax and semantics for interface and implementation sections of the module. Wouldn't be surprised if Modula-2 and Ada had that too.
- wruza 2y agoI remember int/impl sections since the 1990’s turbo pascal, which wasn’t “object” still, iirc. Also, commercial closed-source units (modules) were often distributed in a .tpu/.dcu + .int form, where .int was basically its source code without the implementation section.
- fuzztester 2y agoInteresting. Yes, I remember the .tpu and .dcu filename extensions. IIRC, .tpu stood for turbo pascal unit, and .dcu may have meant delphi compiled unit, not sure of the latter. I don't remember the .int extension, but it would have been there, of course, if you say so. What was the use of the .int file?
- wruza 2y agoIt was literally the unit with implementation part just missing. Sort of a header that you can just read(?). Idk if it played a role in compilation, probably not. But some commercial libraries packaged them as well. Here, look at this random repo: https://github.com/keskival/turbo-pascal-experiments/tree/master/UNITS https://github.com/keskival/turbo-pascal-experiments/tree/ma... -- few int files at the end of a list. This page mentions a few ints without any context: https://comp.lang.pascal.borland.narkive.com/1B3WeJkX/rebuild-of-turbo-pascal-exe-missing-tpu-modules https://comp.lang.pascal.borland.narkive.com/1B3WeJkX/rebuil... This guy seems to package ints for documentation purposes: https://www.wrotniak.net/hplx/lxtpgr.html https://www.wrotniak.net/hplx/lxtpgr.html Man this is nostalgic... T-T
- fuzztester 2y agoGot it, thanks.
- trenchgun 2y agoOCaml .mli interface files are the same, but better.
- pjmlp 2y agoAvailable in most compiled module languages, either separately, Modula-2, Modula-3, Ada, Standard ML, Caml Light, OCaml, F#, D. Or it can be generated either as text, or graphical tooling, Object Pascal, D, Haskell, Java, C#, F#, Swift, Go, Rust. All with stronger typing, faster compilation (Rust and Swift toolchain still need some work), proper namespacing. Unfortunately C tooling has always been more primitive than what was happening outside Bell Labs, and had AT&T been allowed to take commercial advantage, history would be much different, instead we got free lemons, instead of nice juicy oranges. At least they did come up with TypeScript for C, and it nowadays supports proper modules, alongside bounds checked collection types.
- deleted 2y ago[deleted]
- wruza 2y agoI used to think like this, but then I discovered generating (prj_root)/types.d.ts. It doesn’t do anything technical because types are in src/**/*, but I do that to generate a quick overview for a project I’m returning to after a month. Maintaining header files is tedious and I often resorted to a kind of “OBHF.h” for common types, if you know what I mean. Otherwise it’s too much cross-tangling and forwards. Even in ts I do type-only src/types.ts for types likely common to everything, mostly because I don’t want pages of picky this-from-there this-from-there imports in every module. As for public/private and sharing “friends” across implementation modules, we didn’t invent anything good anyway. I just name my public private symbols impl_foo and that tells me and everyone what it is. That said, I wouldn’t want to make html out of it like these *-doc tools do. Using another program to navigate what is basically code feels like their editor sucks. My position on in-code documentation is that it should be navigatable the same way you write it. External tools and build steps kill “immersion”.
- jrmg 2y agoI think there may be a difference in thinking that underlies the difference in opinion here. In my experience, having a header file nudges you to think about interface being a _different thing_ to implementation - something that (because you need to) you think about as more fundamentally separate from the implementation. Folks who think this way bristle at the idea that interface be generated using tooling. The interface is not an artifact of the implementation - it’s a separate, deliberate, and for some even more important thing. It makes no sense to them that it be generated from the implementation source - that’s an obvious inversion of priority. Of course, the reverse is also true - for folks used to auto-generated docs, they bristle at the idea that the interface is not generated from the one true source of truth - the implementation source. To them it’s just a reflection of the implementation and it makes no sense to do ‘duplicate’ work to maintain it. Working in languages with or without separate interface files nudges people into either camp over time, and they forget what it’s like to think in the other way.
- estebank 2y agoThis thread feels weird to me because when I write code I do think about my public API, have even sketched it out separately looking at the desired usage pattern, but never felt the need to save that sketch as anything other than as part of the documentation. Which lives next to the code that implements that API. I think it is telling that the handful of languages that still have something akin to .h files use them purely to define cross-language APIs.
- juped 2y agoI would generate implementations from interfaces were it possible, but I never want to generate interfaces from implementations.
- kode-tar-gz 2y agoWhy not?
- thayne 2y agoI find it pretty frustrating to have the documentation in a different file from the source code. When maintaining the code that means I have to go to a separate file to read what a function is supposed to do, or update the documentation. And when reading the documentation, if the documentation is unclear, I have to go to a separate file to see what the function actually does. Granted, the implementation can get in the way if you are just reading the documentation, but if you aren't concerned about the implementation, then as others have said, you can use generated documentation.
- Lucasoato 2y agoIf you want something similar in Python, you could structure your code following the port and adapter pattern. Very effective, especially if paired with hexagonal architecture and type checking libraries like pydantic.
- paulddraper 2y agoTypeScript has that. Although it can infer types and generate the declaration fully
- coldtea 2y ago>Header files are one of the things I miss the most about languages that aren't C. Having a very clear distinction between public and private, and interface and implementation is one of my favourite things about C code (at least the way I write it). You don't need header files for that. A dead simple to write tool that parses the actual non-duplicating info, files, and prints you the interfaces and access qualifiers for each method as such, presenting you everything or just the public ones etc, should suffice.