alt.hn

1/11/2025 at 10:58:22 PM

Obvious things C should do

https://www.digitalmars.com/articles/Cobvious.html

by LorenDB

1/12/2025 at 4:06:20 AM

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.

by TheNewAndy

1/12/2025 at 4:59:19 AM

> 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.

by kouteiheika

1/12/2025 at 5:27:36 AM

This is probably something where it comes down to preference and familiarity. I would much prefer a simple text file for documentation that I can grep, open in my text editor, modify easily without switching context (oh, I should have been more explicit in the documentation I wrote - let me just fix that now), etc. All the features you mentioned "nice interface, fully searchable API interface, whole public API" are exactly what you get if you open a well written header file in any old text editor.

I used to be a big fan of doxygen etc, but for the stuff I've worked on, I've found that "pretty" documentation is way less important than "useful" documentation, and that the reformatting done by these tools tends to lead towards worse documentation with the people I have worked with ("Oh, I need to make sure every function argument has documentation, so I will just reword the name of the argument"). Since moving away from doxygen I have stopped seeing this behaviour from people - I haven't tried to get a really good explanation as to why, but the quality of documentation has definitely improved, and my (unproven) theory is that keeping the presentation as plain as possible means that the focus turns to the content.

I don't know if rust doc suffers the same issues, but the tooling you are mentioning just seems to add an extra step (depending on how you count steps I suppose, you could perhaps say it is the same number of steps...) and provide no obvious benefit to me (and it does provide the obvious downside that it is harder to edit documentation when you are reading it in the form you are suggesting).

But with all these things, different projects and teams and problem domains will probably tend towards having things that work better or worse.

by TheNewAndy

1/12/2025 at 6:03:08 AM

> well written text file

The problem with this is no one agrees on the definition of "well-written", so consistency is a constant battle and struggle. Language tooling is a better answer for quality of life.

by metadat

1/12/2025 at 6:18:51 AM

That's an interesting assertion, but not one that matches the experience I've had.

It is one of those things that sounds "obviously true", but in practice I've found that it doesn't really live up to the promise. As a concrete example of this, having a plain text header file as documentation tends to mean that when people are reading it, if they spot a mistake or see that something isn't documented that should be documented, they are much more likely to fix it than if the documentation is displayed in a "prettier" form like HTML.

The problem with header files that aren't "well-written" tends to be that the actual content you are looking for isn't in there, and no amount of language tooling can actually fix that (and can be an impediment towards fixing it).

by TheNewAndy

1/12/2025 at 6:28:01 AM

I'll second this in Java land. I much prefer reading the sources directly than javadocs. Though jshell also comes in handy.

by tpoacher

1/12/2025 at 7:15:15 AM

I have the same experience a lot of the time with 3rd party rust crates. Doc.rs is amazing - but it’s rare that I’ll use a library without, at some point, hitting view source.

by josephg

1/12/2025 at 7:42:41 AM

for most rust ive done (not tons) the docs were very basic as onky auto generated with minimal content. totally useless, have to read sources to find out what is in there. auto documentation to me ia just ti satisfy people who need to tick all of these boxes and want to do with minimal effort. has dox has tests etc. such artitude never leads to quality.

by sim7c00

1/12/2025 at 12:04:52 PM

Useful documentation is impossible for the developer to write - they are too close to the code and so don't understand what the users (either api users or end users) need to know. Developers agonize over details that users don't care about while ignoring as obvious important things users don't know.

by bluGill

1/12/2025 at 7:27:16 AM

I know people look at me like I’m a heathen and a scoundrel, but I think a lot of software teams spend too much time trying to make things consistent. Where’s the ROI? There is none.

GitHub readmes? Bring on the weird quirks, art, rants about other software, and so on. I’ll take it all.

Don’t get me started on linters. Yes, there’s lots of things that should actually be consistent in a codebase (like indentation). But for every useful check, linters have 100 random pointless things they complain about. Oh, you used a ternary statement? Boo hoo! Oh, my JavaScript has a mix of semicolons and non semicolons? Who cares? The birds are singing. Don’t bother me with this shite.

Software is a creative discipline. Bland software reflects a bland mind.

by josephg

1/12/2025 at 6:59:25 PM

> Where’s the ROI? There is none.

> Oh, my JavaScript has a mix of semicolons and non semicolons? Who cares?

i had to refactor and port a javascript codebase that contained a mix of all of javascripts syntactic sugar, no comments anywhere in the codebase, and i was unable to ask the original devs any questions. the high amount of syntactic sugar gave me "javascript diabetes" - it was fun figuring out all the randomness, but it delayed the project and has made it extremely difficult to onboard new folks to the team after i completed the port.

painting is a creative discipline, and the mona lisa has stood the test of time because davinci used a painting style and materials that set the painting up for long term use.

a codebase without standards is akin to drawing the mona lisa on a sidewalk with sidewalk chalk.

by lazystar

1/12/2025 at 9:56:04 AM

I don't like complaining linters. I do like auto fixing linters I can leave running in the background.

by XorNot

1/12/2025 at 7:01:25 PM

> auto fixing linters

any advice on how to implement an auto linter in an old codebase? i hate losing the git blame info.

by lazystar

1/12/2025 at 6:07:01 PM

Have you looked at how OCaml does it?

The historical way is to have a .ml and a .mli files. The .ml file contains the implementation. Any documentation in that file is considered implementation detail, will not be published by ocamldoc. The .mli file contains everything users need to know, including documentation, function signatures, etc.

Interestingly, the .mli and the .ml signatures do not necessarily need to agree. For instance, a global variable in the .ml does not need to be published in the .mli. More interestingly, a generic function in the .ml does not need to be exposed as generic in the .mli, or can have more restrictions.

You could easily emulate this in Rust, but it's not the standard.

by Yoric

1/12/2025 at 4:44:54 PM

> and that the reformatting done by these tools tends to lead towards worse documentation with the people I have worked with ("Oh, I need to make sure every function argument has documentation, so I will just reword the name of the argument")

That seems like an orthogonal issue to me. I've seen places where documentation is only in the source code, no generated web pages, but there is a policy or even just soft expectation to document every parameter, even if it doesn't dd anything. And I've also seen places that make heavy use of these tools that doesn't have any such expectation.

by thayne

1/12/2025 at 6:31:39 AM

> All the features you mentioned "nice interface, fully searchable API interface, whole public API" are exactly what you get if you open a well written header file in any old text editor.

No, you can't, and it's not even close.

You have a header file that's 2000 lines of code, and you have a function which uses type X. You want to see the definition of type X. How do you quickly jump to its definition with your "any old text editor"? You try to grep for it in the header? What if that identifier is used 30 times in that file? Now you have to go through all of other 29 uses and hunt for the definition. What if it's from another header file? What if the type X is from another library altogether? Now you need to manually grep through a bunch of other header files and potentially other libraries, and due to C's include system you often can't even be sure where you need to grep on the filesystem.

Anyway, take a look at the docs for one of the most popular Rust crates:

https://docs.rs/regex/1.11.1/regex/struct.Regex.html

The experience going through these docs (once you get used to it) is night and day compared to just reading header files. Everything is cross linked so you can easily cross-reference types. You can easily hide the docs if you just want to see the prototypes (click on the "Summary" button). You can easily see the implementation of a given function (click on "source" next to the prototype). You can search through the whole public API. If you click on a type from another library it will automatically show you docs for that library. You have usage examples (*which are automatically unit tested so they're guaranteed to be correct*!). You can find non-obvious relationships between types that you wouldn't get just by reading the source code where the thing is defined (e.g. all implementations of a given trait are listed, which are usually scattered across the codebase).

> I don't know if rust doc suffers the same issues, but the tooling you are mentioning just seems to add an extra step (depending on how you count steps I suppose, you could perhaps say it is the same number of steps...) and provide no obvious benefit to me (and it does provide the obvious downside that it is harder to edit documentation when you are reading it in the form you are suggesting).

Why would I want to edit the documentation of an external library I'm consuming when I'm reading it? And even if I do then the effort to make a PR changing those docs pales in comparison to the effort it takes to open the original source code with the docs and edit it.

Or did you mean editing the docs for my code? In that case I can also easily do it, because docs are part of my source files and are maintained alongside the implementation. If I change the implementation I have docs right there in the same file and I can easily edit them. Having to open the header file and hunt for the declaration to edit the docs "just seems to add an extra step" and "and provide no obvious benefit to me", if I may use your words. (:

by kouteiheika

1/12/2025 at 8:55:25 AM

Thanks for the constructive example of the rust doc.

I am not making things up when I say that the very first question I had about how to use this module, either is not answered, or I couldn't find the answer. That question was "what regular expression syntax is supported?". This is such a fundamental question, yet there is no answer provided.

As a preference thing, I don't really like examples in APIs (it is supposed to be a reference in my opinion) and I find them to be mostly noise.

> Why would I want to edit the documentation of an external library I'm consuming when I'm reading it? And even if I do then the effort to make a PR changing those docs pales in comparison to the effort it takes to open the original source code with the docs and edit it.

Right, this is possibly where our experiences differ. I'm frequently pulling in loads of code, some of which I've written, some of which other people have written, and when I pull in code to a project I take ownership of it. Doesn't matter who wrote it - if it is in my project, then I'm going to make sure it is up to the standards I expect. A lot of the time, the code is stuff I've written anyway, which means that when I come back in a few months time and go to use it, I find that things that seemed obvious at the time might not be so obvious, and a simple comment can completely fix it. Sometimes it is a comment and a code change ("wouldn't it be nice if this function handled edge case X nicely? I'll just go in there and fix it").

The distinction between external and internal that you have looks pretty different to me, and that could just be why we have different opinions.

by TheNewAndy

1/12/2025 at 9:55:37 AM

The parent linked to a subsection showing usage for a particular object. If you click back into the root level for the document there is a header specifying ‘syntax’, and other more ‘package-level’ documentation

by davisoneee

1/12/2025 at 11:02:06 AM

> I am not making things up when I say that the very first question I had about how to use this module, either is not answered, or I couldn't find the answer. That question was "what regular expression syntax is supported?". This is such a fundamental question, yet there is no answer provided.

This is a fair question to have. As others have already said, this is the API reference for a particular class, so you won't get the high level details here. You can click in the upper left corner to go to the high level docs for the whole library.

> The distinction between external and internal that you have looks pretty different to me, and that could just be why we have different opinions.

Well, there are two "external" vs "internal" distinctions I make:

1. Code I maintain, vs code that I pull in as an external dependency from somewhere else (to give an example, something like libpng, zlib, etc.). So if I want to fix something in the external dependency I make a pull request to the original project. Here I need to clone the original project, find the appropriate files to edit, edit them, make sure it compiles, make sure the tests pass, make a PR, etc. Having the header file immediately editable doesn't net me anything here because I'm not going to edit the original header files to make the change (which are either installed globally on my system, or maintained by my package manager somewhere deep under my /home/).

2. Code that is part of my current project, vs code that is a library that I reuse from another of my projects. These are both "internal" in a sense that I maintain them, but to my current project those are "external" libraries (I maintain them separately and reuse in multiple projects, but I don't copy-paste them and instead maintain only one copy). In this case it's a fair point that if you're browsing the API reference it's extra work to have to open up the original sources and make the change there, but I disagree that it's making things any harder. I still have to properly run any relevant unit tests of the library I'm modifying, still have to make a proper commit, etc., and going from the API reference to the source code takes at most a few seconds (since the API reference will tell me which exact file it is, so I just have to tell my IDE's fuzzy file opener to open up that file to me.) and is still a tiny fraction of all of the things I'd need to do to make the change.

by kouteiheika

1/12/2025 at 9:39:19 AM

> I am not making things up when I say that the very first question I had about how to use this module, either is not answered, or I couldn't find the answer. That question was "what regular expression syntax is supported?". This is such a fundamental question, yet there is no answer provided.

The main page for the documentation answers that question: https://docs.rs/regex/1.11.1/regex/index.html

It even says "If you just want API documentation, then skip to the Regex type", which is what you were linked to before.

by reanimus

1/12/2025 at 7:58:39 AM

Most decent text editors support something like go to definition. Your entire comment seems to be based on the idea that text editors only support basic search, which is simply false.

Personally I'm quite content with both experiences. But it really is just a matter of preference.

by mtlmtlmtlmtl

1/12/2025 at 8:02:50 AM

At least moderately advanced text editors often interoperate with symbols tables, so you can jump to a definition. But even with grep, you can usually do it in a way where you differentiate between definition and use. But I am not arguing that you should not use advanced tools if you like is, the deeper point is that you can always use advanced tools even with headers, but you can not go back in a language designed around advanced tools and work with simple tools. So it is strictly inferior IMHO to design a language around this additional cmplexity.

by uecker

1/12/2025 at 11:10:01 AM

I think the person you're responding to must know all of this. This is stuff that's obvious to anyone who has ever written any code that required using libraries. Unfortunately , people like to pretend to have a gripe with something on the internet just for the sake of arguing. This is the only conclusion I can arrive at when people appear to seriously propose reading a header file in a text editor is somehow better than reading documentation in a purposefully designed documentation format. It's like saying browsers are just a waste of time when you can just use Gopher for everything.

by brabel

1/12/2025 at 3:57:54 PM

Or it just might be that different people prefer different things. I'm a hardcore fan of header files too. Vim is my preferred way of dealing with text and I can do all kinds of magic with it with the speed of though and I prefer to use as plain as possible text files. In the rare occasion when the documentation needs more than ascii stuff it's best practice to write a nice tex and friends documentation plus a real tutorial anyway. And full literate programming style is hard to beat when you are dealing with complex things.

by hyperold

1/12/2025 at 4:15:03 PM

It's fine to have preferences or cognitive inertia towards working a certain way. It's silly to pretend that doing things this way conveys some kind of universalist advantage or to conjure up a bunch of imaginary/highly niche scenarios (I'm remote coding over 28.8k at the bottom of the ocean and have no access to a browser anywhere!) that necessitate working this way for argumentative purposes.

by almostdeadguy

1/12/2025 at 4:20:50 PM

The OP uses C libraries, and this is used to much simpler interfaces and much smaller dependency sets than the GP. So no, I don't think they know all of this.

But also, they probably to know how to keep their dependencies sane, and possibly think the best way to document that giant 2k lines interface is in a book. What are both really good opinions, that will never be really "understood" by communities the GP takes his libraries from just because it's not viable for them to do it.

by marcosdumay

1/12/2025 at 9:31:55 AM

Depending on coding style you could just do something like this:

  ^struct whatever

by hawski

1/12/2025 at 6:12:16 PM

The issue is that the coding style depends on whoever wrote the external library, not on you, so this ends up working only sometimes. You can probably find some other combination that will help you find what you're looking for (I do this all the time when using Github's web interface) but ultimately this is just a bad experience.

by SkiFire13

1/12/2025 at 5:27:36 AM

As 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.

by panic

1/12/2025 at 5:36:27 AM

> Designing the interface separately from the implementation feels good to me

would you make the same argument for java then?

by chii

1/12/2025 at 5:58:02 PM

It’s important to not conflate Java’s interface keyword with the more general notion of “interface” as in “API”. In Java, you generally don’t and can’t have a source-level representation of the API without it being interspersed with its implementation.

(You could imagine such a representation, i.e. remove all method bodies and (package-)private elements, but the result wouldn’t be valid Java. IDEs arguably could and should provide such a source-level view, e.g. via code folding, but I don’t know any that do.)

by layer8

1/12/2025 at 3:10:21 PM

Even 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.

by marginalia_nu

1/13/2025 at 4:27:27 AM

> java interfaces also have a lot of weird stuff going on with them.

really? I know java pretty well, and there aint nothing weird there.

C uses conventions to produce an "interface" (ala a header file with declarations). Java uses compilers to produce an interface, which i do really like. You can ship that interface without an implementation, and only at runtime load an implementation for example.

> You both have class/interface, but also public/private.

And these are all othorgonal concerns. A private interface is for the internal organization of code, as opposed to a public one (for external consumption). That's why you might have a private interface.

by chii

1/13/2025 at 3:16:17 PM

> C uses conventions to produce an "interface" (ala a header file with declarations). Java uses compilers to produce an interface, which i do really like. You can ship that interface without an implementation, and only at runtime load an implementation for example.

You can do this in C as well, since it has separate code declarations and definitions. You conventionally put the declarations in the header and definitions in the source file. A C program can link against declarations alone, and the implementations can be loaded later using dynamic linking.

> And these are all othorgonal concerns. A private interface is for the internal organization of code, as opposed to a public one (for external consumption). That's why you might have a private interface.

But these are profoundly overlapping concerns. Interfaces also hide the internal organization of the code, and on top of this, you also have project jigzaw's modules (that absolutely nobody uses), but which also caters toward separating private implementations from public interfaces.

by marginalia_nu

1/12/2025 at 6:14:36 AM

Absolutely not.

by otteromkram

1/12/2025 at 6:29:16 AM

you don't like java interfaces?

by tpoacher

1/12/2025 at 9:01:56 AM

The problem is that you have to write the interface not only separately grom the implementation, but together with the implementation as well, which leads to duplication of information;

by xigoi

1/12/2025 at 12:10:15 PM

So? Yes you need some duplicatin but it forces you to put information useful to api users where they won't have to wade through pages of information not of interest to them. eventually they may need to read the source but a lot of common questions are answered by the header and so the small cost to make them is worth it.

of course javadoc can answer the same questions but then you have to run it.

by bluGill

1/12/2025 at 5:14:52 PM

The include file mechanism is a hack that was acceptable at the time when machines were extremely underpowered, so only the simplest solutions had a chance to be implemented within a reasonable time frame.

By now, of course, precompiled headers exist, but their interplay with #define allows for fun inconsistencies.

And, of course, they leak implementation as much as the author wants, all the way to .h-only single-file libraries.

If you want an example of a sane approach to separate interface and implementation files from last century, take a look e.g. at Modula-2 with its .int and .mod files.

by nine_k

1/12/2025 at 7:22:23 PM

Precompiled headers are a terrible misfeature. I ban them in any code base I am responsible for.

They encourage the use of large header files that group unrelated concerns. In turn that makes small changes in header files produce massive, unnecessary rebuilds of zillions of object files.

The clean practice is to push down #includes into .c files, and to ruthlessly eliminate them if at all possible. That speeds up partial rebuilds enormously. And once you adopt that clean practice, pre-compiled headers yield no benefit anyway.

by alextingle

1/12/2025 at 7:42:58 PM

Modules already existed in programming languages outside Bell Labs in the same decade, like the Modula-2 you quote.

by pjmlp

1/12/2025 at 5:29:47 AM

I’m with parent - what if you don’t have the tool? What if there’s a syntax error in some implementation or dependency such that the tool chokes early?

Human readable headers are accessible out of context if the implementation. They also help provide a clear abstraction - this is the contract. This is what I support as of this version. (And hopefully with appropriate annotations across versions)

by salgernon

1/12/2025 at 6:05:00 AM

> I’m with parent - what if you don’t have the tool?

The "what if you don't have the tool" situation never happens in case of Rust. If you have the compiler you have the tool, because it's always included with the compiler. This isn't some third party tool that you install manually; it's arguably part of the language.

> What if there’s a syntax error in some implementation or dependency such that the tool chokes early?

In C I can see how this can happen with its mess of a build systems; in Rust this doesn't happen (in my 10+ years of Rust I've never seen it), because people don't publish libraries with syntax errors (duh!).

by kouteiheika

1/12/2025 at 6:23:39 AM

"Never" is a big call.

In this specific case, your tool requires a web browser (though I'm assuming that there is a non-web browser form of what is being sold here). Maybe you are in a situation where you only have terminal access to the machine.

Maybe you are on your phone just browsing github looking for a library to use

I'm sure people can continue to imagine more examples. It is entirely possible that we have different experiences of projects and teams.

by TheNewAndy

1/12/2025 at 7:39:16 AM

> I’m sure people can continue to imagine more examples

Hopefully they’ll imagine more compelling examples.

If the hypothetical person’s phone is capable of browsing GitHub, I don’t see why they can’t also browse docs.rs. It renders well on small screens. That’s not a hypothetical, I’ve actually read the docs for libraries on my phone.

by nindalf

1/12/2025 at 6:38:37 AM

> The "what if you don't have the tool" situation never happens in case of Rust.

So it’s built into GitLab and GitHub? BitBucket? How easy is it to use on windows (i.e. is it is easy as opening a .h in notepad and reading it)? How easy is it to use from a command line environment with vim or emacs bindings?

I could go on. “Never” is doing a lot of heavy lifting in your assertion. I shouldn’t have to install a toolchain (let alone rely on a web browser) to read API documentation.

by jmb99

1/12/2025 at 7:33:13 AM

> I could go on

Please do. It just sounds like you’re nitpicking.

If you can open a browser, open docs.rs. The GitHub repo usually contains a link to docs.rs because that’s how people prefer to read the documentation.

If you prefer working without the internet that’s fine too. Use cargo doc, which opens the rendered doc page in a local web browser.

If you prefer being in a text editor exclusively, no problem! Grep for `pub` and read the doc comments right above (these start with ///). No toolchain necessary.

Look, most normal people don’t have some intense phobia of web browsers, so they’d prefer docs.rs. For the people who prefer text editor, it’s still a great experience - git clone and look for the doc comments.

The point is, the existence of docs.rs only encourages Rust library developers to write more and better documentation, which everyone, including text editor exclusive people benefit from. That’s why your comment sounds so strange.

by nindalf

1/12/2025 at 11:50:42 AM

[flagged]

by devvvvvvv

1/12/2025 at 12:31:54 PM

Most normal people don't read documentation of programming libraries.

by hooli_gan

1/12/2025 at 4:26:34 PM

Most normal people don't comment on hackernews, yet here we are.

by estebank

1/12/2025 at 7:51:00 AM

> So it’s built into GitLab and GitHub? BitBucket?

No. It's built into the toolchain which every Rust developer has installed.

> How easy is it to use on windows (i.e. is it is easy as opening a .h in notepad and reading it)?

A easy as on Linux or macOS from my experience.

> How easy is it to use from a command line environment with vim or emacs bindings?

Not sure I understand the question; use how exactly? You either have a binding which runs `cargo doc` and opens the docs for you, or you use an LSP server and a plugin for your editor in which case the docs are integrated into your editor.

> I shouldn’t have to install a toolchain (let alone rely on a web browser) to read API documentation.

If you want you can just read the source code, just as you do for any other language, because the docs are right there in the sources.

For publicly available libraries you can also type in `https://docs.rs/$name_of_library` in your web browser to open the docs. Any library available through crates.io (so 99.9% of what people use) have docs available there, so even if you don't have the toolchain installed/are on your phone you can still browse through the docs.

I know what you're going to say - what if you don't have the toolchain installed and the library is not public? Or, worse, you're using a 30 year old machine that doesn't have a web browser available?! Well, sure, tough luck, then you need to do it the old school way and browse the sources.

You can always find a corner case of "what if...?", but I find that totally unconvincing. Making the 99.9% case harder (when you have a web browser and a toolchain installed, etc.) to make the 0.1% case (when you don't) easier is a bad tradeoff.

by kouteiheika

1/12/2025 at 8:58:17 AM

I don't understand how you don't understand the order of magnitude difference in flexibility, utility, availability, etc between needing to run a specific executable vs merely opening a text file in any way.

"you always have the exe" is just not even remotely a valid argument.

by Brian_K_White

1/12/2025 at 11:31:21 AM

> "you always have the exe" is just not even remotely a valid argument.

Why? Can you explain it to me?

I'm a Rust developer. I use my work station every day for 8 hours to write code. I also use `cargo doc` (the tool for which "I always have the exe") every day to look up API docs, and in total this saves me a ton of time every month (probably multiple hours at least, if I'm working with unfamiliar libraries), and I save even more time because I don't have to maintain separate header files (because Rust doesn't have them).

Can you explain the superior flexibility and utility of "merely opening a text file" over this approach, and how that would make me (and my colleagues at work) more productive and save me time?

I'm not being sarcastic here; genuinely, please convince me that I'm wrong. I've been a C developer for over 20 years and I did it the "opening a text file" way and never want to go back, but maybe you're seeing something here that I never saw myself, in which case please enlighten me.

by kouteiheika

1/12/2025 at 4:42:38 PM

It's less available in rare situations.

It's not less flexible once you already took availability into account.

It has more utility, that's the entire point.

by Dylan16807

1/12/2025 at 9:56:47 AM

I don’t understand how you don’t understand that that’s always an option. Rust source files are written in plaintext too.

There are a few people in this thread, including you, who claim that they vastly prefer the output of documentation to be plain text in a single file rather than linked HTML files OR reading the source in multiple plaintext files.

That’s a preference, so y’all can’t be wrong. But consider that if this preference was even slightly popular, cargo doc would probably get a —-text option that output everything in a single text file. The fact that it doesn’t have it tells me that this preference is very niche.

by nindalf

1/12/2025 at 11:49:17 AM

[flagged]

by devvvvvvv

1/12/2025 at 4:31:22 PM

Yes, it works with GitHub, GitLab, Bitbucket, and everything else. It's built into the compiler toolchain.

It works with every syntax that you can compile, because it uses the compiler itself to extract the documentation.

Yes, it works on Windows too. Rust supports Windows as a first-class platform. It works with dependencies too (the docs even link across packages). The fragmentation of C tooling and unreliability/complexity of integrating with C builds is not a universal problem.

Rust's built-in documentation generator creates HTML, so anything with a browser can show it. It also has JSON format for 3rd party tooling.

The same language syntax for the documentation is understood by Rust's LSP server, so vim, emacs, and other editors with LSP plugins can show the documentation inline too.

I've been using this for years, and it works great. I don't miss maintaining C headers at all. I write function definitions once, document them in the same place where the code is, and get high fidelity always up-to-date API docs automatically.

by pornel

1/12/2025 at 7:19:01 AM

> I shouldn’t have to install a toolchain (let alone rely on a web browser) to read API documentation.

Why are you reading a library API for a language you're not coding in?

I'm sure you can come up with some situation, but that situation should NOT be what we optimize for.

And web browsers are fine.

> is it is easy as opening a .h in notepad and reading it

If you include the actual ease of reading, yeah it should be.

by Dylan16807

1/12/2025 at 6:50:47 AM

The "what if you don't have the software" argument doesn't hold water for me. What if you don't have git? What if you don't have a text editor? What if you don't have a filesystem?

Most programming language communities are okay with expecting a certain amount of (modern) tooling, and C can't rely on legacy to remain relevant forever...

by gary_0

1/12/2025 at 7:11:29 AM

Sounds like COM/DCOM from ~1995. Every API had a public interface including a description. You could open the DCOM Inspector, browse all the APIs, and see the type signature of every function and its docs.

by nox101

1/12/2025 at 7:53:30 AM

Still is COM from 2025, given its relevance on Windows, even more since Vista, as all Longhorn ideas were remade in COM.

However the tooling experience is pretty much ~1995, with the difference IDL is at version 3.0.

by pjmlp

1/12/2025 at 8:51:07 AM

headers perform the same job for all code, not just code that's in some library.

Frankly your description of what you just called easy sounds terrible and pointlessly extra, indirection that doesn't pay for itself in the form of some overwhelming huge win somewhere else. It's easy only if the alternative was getting it by fax or something.

by Brian_K_White

1/12/2025 at 4:50:02 PM

Having to make an entire separate file to mark something as public rather than just having a keyword in the language sounds to me "terrible and pointlessly extra". It's not like you can't just put all your public stuff in it's own file in Rust rather than putting private stuff in it as well; empirically though, people don't do this because it's just not worth the effort.

by saghm

1/12/2025 at 5:24:12 PM

Think about closed-source software that has to export a low-level programmatic interface. You may not believe it, but it's still widespread.

by nine_k

1/12/2025 at 5:00:12 AM

Header 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.

by kevin_thibedeau

1/12/2025 at 3:12:24 PM

I 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.

by jrmg

1/12/2025 at 4:34:29 PM

This 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.

by estebank

1/12/2025 at 4:35:52 PM

I would generate implementations from interfaces were it possible, but I never want to generate interfaces from implementations.

by juped

1/12/2025 at 5:01:16 PM

Why not?

by kode-tar-gz

1/12/2025 at 6:11:10 AM

I 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.

by m463

1/12/2025 at 8:01:49 AM

Available 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.

by pjmlp

1/12/2025 at 4:37:52 PM

I 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.

by thayne

1/12/2025 at 10:12:37 AM

I 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”.

by wruza

1/12/2025 at 4:10:09 AM

Some other languages have equivalents (OCaml comes to mind), but usually they’re less necessary

by legobmw99

1/12/2025 at 4:27:44 AM

I 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).

by Lvl999Noob

1/12/2025 at 4:40:56 AM

That 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.

by TheNewAndy

1/12/2025 at 4:53:02 AM

I 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.

by Lvl999Noob

1/12/2025 at 12:15:34 PM

A compiler can still have access to the internal layout in C via link-time optimization.

by uecker

1/12/2025 at 3:23:07 PM

Linker is something different from Compiler (even if often called via same Frontend)

by johannes1234321

1/12/2025 at 3:38:36 PM

The linker invokes the compiler again.

by uecker

1/12/2025 at 5:35:06 AM

Personally, 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.

by TheNewAndy

1/12/2025 at 5:09:20 AM

I think that's a problem with C's header files.

With C++ you have the third option where the compiler makes sure that the "people will do the right thing" with the private keyword - assuming they're not doing some weird pointer math to access the private members..

Of course, you'll have to deal with ABI stability now but it's all tradeoffs for what your requirements are.

by saidinesh5

1/12/2025 at 6:39:38 AM

Of course you should do the right thing, but if you want to break the private of C++ it is much easier to "#define private public" before including the header file.

by Mankaninen

1/12/2025 at 2:08:34 PM

now that's just diabolical...

by saidinesh5

1/12/2025 at 5:15:16 AM

Right, but as soon as you have private stuff in your header file, that is leaking implementation details. Yes it is kind of true that these are compile time checked to make sure that people don't do the wrong thing, but it is still an implementation detail that is leaking.

It comes down to a cost benefit thing - is the cost of poorer readability worth it for mitigating the risk of people doing the wrong thing? My experience says no, other people's experience says yes. Probably we are working on different problems and with different teams.

by TheNewAndy

1/12/2025 at 8:05:50 AM

In 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.

by uecker

1/12/2025 at 5:28:46 PM

Even 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.

by johannes1234321

1/12/2025 at 6:47:30 AM

The 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.

by lzsiga

1/12/2025 at 6:20:34 AM

Object 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.

by fuzztester

1/12/2025 at 7:55:23 PM

That was inherited from UCSD Pascal, and also incorporated into ISO Extended Pascal, which was supposed to be a more industry friendly revision of ISO Pascal, but by then Object Pascal was the de facto standard.

Modula-2 modules are based on Xerox Mesa, and do have split sections, as does Ada.

Additionally, Modula-2 and Ada modules/packages have a powerful feature that is seldom used, multiple interfaces for the same module implementation, this allows to customise the consume of a module depending on the customer code.

by pjmlp

1/12/2025 at 10:20:11 AM

I 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.

by wruza

1/12/2025 at 9:05:09 PM

Interesting.

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?

by fuzztester

1/12/2025 at 10:29:18 PM

It 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/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/rebuil...

This guy seems to package ints for documentation purposes: https://www.wrotniak.net/hplx/lxtpgr.html

Man this is nostalgic... T-T

by wruza

1/17/2025 at 2:59:24 PM

Got it, thanks.

by fuzztester

1/12/2025 at 7:43:05 AM

OCaml .mli interface files are the same, but better.

by trenchgun

1/12/2025 at 5:03:34 AM

They are also somewhat of hassle and something not necessary to have.

by billfruit

1/13/2025 at 1:01:53 AM

>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.

by coldtea

1/12/2025 at 5:32:02 AM

I 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.

by harisund1990

1/12/2025 at 6:28:45 AM

It 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).

by lzsiga

1/12/2025 at 8:43:07 AM

Also, 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.

by chikere232

1/12/2025 at 5:24:54 PM

If 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.

by Lucasoato

1/12/2025 at 5:20:52 AM

Header files also make it a lot more obvious how you're supposed to distribute a library as a binary, which is good.

by ryukoposting

1/12/2025 at 10:15:27 PM

TypeScript has that.

Although it can infer types and generate the declaration fully

by paulddraper

1/11/2025 at 11:41:53 PM

While the author has WAY more knowledge/experience than me on this and so I wonder how he would solve the following issues:

Evaluating Constant Expressions

- This seems really complicated...if you're working within a translation unit, thats much simplified, but then you're much more limited in what you can do without repeating a lot of code. I wonder how the author solves this.

Compile Time Unit Tests

- This is already somewhat possible if you can express your test as a macro, which if you add in the first point, then this becomes trivial.

Forward Referencing of Declarations

- I think there may be a lot of backlash to this one. The main argument against this is that it then changes the compiler from a one-pass to two pass compiler which has its own performance implications. Given the number of people who are trying to compile massive codebases and go as far as parallelizing compilation of translation units, this may be a tough pill for them to swallow. (evaluating constant expressions probably comes with a similar/worse performance hit caveat depending on how its done)

Importing Declarations

- This is a breaking change...one of the ways I have kind of implemented templating in C is by defining a variable and importing a c file, changing the variable, and then reimporting the same c file. Another thing I've done is define a bunch of things and then import the SQLite C Amalgamation and then add another function (I do this to expose a SQLite internal which isnt exposed via its headers). All of these use cases would break with this change.

Are there any thoughts about these issues? Any ways to solve them perhaps?

by chacham15

1/12/2025 at 12:04:52 AM

> if you're working within a translation unit, thats much simplified, but then you're much more limited in what you can do without repeating a lot of code. I wonder how the author solves this.

You are correct in that the source code to the function being evaluated must be available to the compiler. This can be done with #include. I do it in D with importing the modules with the needed code.

> This is already somewhat possible if you can express your test as a macro, which if you add in the first point, then this becomes trivial.

Expressing the test as a macro doesn't work when you want to test the function. The example I gave was trivial to make it easy to understand. Actual use can be far more complex.

> Performance

D is faster at compiling than C compilers, mainly because:

1. the C preprocessor is a hopeless pig with its required multiple passes. I know, I implemented it from scratch multiple times. The C preprocessor was an excellent design choice when it was invented. Today it is a fossil. I'm still in awe of why C++ has never gotten around to deprecating it.

2. D uses import rather than #include. This is just way, way faster, as the .h files don't need to be compiled over and over and over and over and over ...

D's strategy is to separate the parse from the semantic analysis. I suppose it is a hair slower, but it also doesn't have to recompile the duplicate declarations and fold them into one.

Compile time function execution can be a bottleneck, sure, but that (of course) depends on how heavily it is used. I tend to use it with a light touch and the performance is fine. If you implement a compiler using it (as people have done!) it can be slow.

> one of the ways I have kind of implemented templating in C is by defining a variable and importing a c file, changing the variable, and then reimporting the same c file. Another thing I've done is define a bunch of things and then import the SQLite C Amalgamation and then add another function (I do this to expose a SQLite internal which isnt exposed via its headers). All of these use cases would break with this change.

I am not suggesting removing #include for C. The import thing would be additive.

> Are there any thoughts about these issues?

If you're using hacks to do templating in C, you've outgrown the language and need a more powerful one. D has top shelf metaprogramming - and as usual, other template languages are following in D's path.

by WalterBright

1/12/2025 at 2:04:16 AM

Thanks for taking the time to respond! I have a few followup questions if thats ok:

> You are correct in that the source code to the function being evaluated must be available to the compiler. This can be done with #include. I do it in D with importing the modules with the needed code.

> D's strategy is to separate the parse from the semantic analysis. I suppose it is a hair slower, but it also doesn't have to recompile the duplicate declarations and fold them into one.

I dont quite follow all the implications that these statements have. Does the compiler have a different way of handling a translation unit?

- Is a translation unit the same as in C, but since you're #including the file you would expect multiple compilations of a re-included C file? woudnt this bloat the resulting executable (/ bundle in case of a library)

- Are multiple translation units compiled at a time? Wouldnt this mean that the entire translation dependency graph would need to be simultaneously recompiled? Wouldnt this inhibit parallelization? How would it handle recompilation? What happens if a dependency is already compiled? Would it recompile it?

> Performance

I think a lot of this is tied to my question about compilation/translation units above, but from my past experience we have "header hygene" which forces us to use headers in a specific way, which if we do, we actually get really good preprocessor performance (a simple example being: dont use #include in a header), how would you compare performance in these kinds of situations vs a compiler without (i.e. either recompiled a full source file or looking up definitions from a compiled source)?

> If you're using hacks to do templating in C, you've outgrown the language and need a more powerful one. D has top shelf metaprogramming - and as usual, other template languages are following in D's path.

yes, as also demonstrated in the performance question, we do a lot to work within the confines of what we have when other tools would handle a lot more of the lifting for us and this is a fair criticism, but on the flip side, I dont have the power to make large decisions on an existing codebase like "lets switch languages" (even if for a source file or two...I've tried) as much as I wish I could, so I have to work with what I have.

by chacham15

1/12/2025 at 3:11:01 AM

> I dont have the power to make large decisions on an existing codebase like "lets switch languages"

We struggled with that for a long time with D. And finally found a solution. D can compile Standard C source files and make all the C declarations available to the D code. When I proposed it, there was a lot of skepticism that this could ever work. But when it was implemented and debugged, it's been a huge win for D.

> Performance

With D you can put all your source files on one command line invocation. That means that imports are only read once, no matter how many times it is imported. This works so well D users have generally abandoned the C approach of compiling each file individually and then linking them together. A vast amount of time is lost in C/C++ compilation with simply reading the .h files thousands of times.

Modules/imports are a gigantic productivity booster. They're not hard to implement, either. Except for the way C++ did it.

> re multiple translation units compiled at a time? Wouldnt this mean that the entire translation dependency graph would need to be simultaneously recompiled? Wouldnt this inhibit parallelization? How would it handle recompilation? What happens if a dependency is already compiled? Would it recompile it?

Yes, yes, yes, yes. And yet, it still compiles faster! See what I wrote above about not needing to read the .h files thousands of times. Oh, and building one large object file is faster than building a hundred and having to link them together.

by WalterBright

1/12/2025 at 1:01:08 PM

I know that in other languages, one obstacle for "just compile the C files" is that the target language might not have pointers and thus have difficulty representing things such as return-by-pointer.

I suppose in D this was less of an issue because D has pointers?

by ufo

1/12/2025 at 6:49:31 PM

I'm not sure what you mean.

by WalterBright

1/12/2025 at 7:16:12 PM

A foreign function interface that's based on parsing C files must translate C types and interfaces into types and interfaces of the target language. I suppose it helped that D's type system has many similarities with C, including support for pointers.

(The issue with return-by-pointer is that in C it's common to use the return value for an error code and use pointer arguments to pass data back to the caller. These are awkward to map to a target language that doesn't have pointers)

by ufo

1/12/2025 at 2:58:21 AM

> Is a translation unit the same as in C, but since you're #including the file you would expect multiple compilations of a re-included C file? woudnt this bloat the resulting executable (/ bundle in case of a library)

I think the idea is that compiling a translation unit produces two outputs, the object code (as it currently does), and an intermediate representation of the exported declarations, that could be basically a generated .h file, but it would probably be more efficient to use a different format. Then dependent translation units use those declaration files.

With this, you can still compile in parallel. You are constrained by the order of dependencies, but that is already kind of the case.

One complication is that ideally, if the signature doesn't change, but the implementation does, you don't need to re-compile dependent translation units. This is trivial if your build system detects changes based on content (like, say, bazel), but if it uses timestamps (like make) then the compiler needs to ensure the timestamp isn't updated when the declarations don't change.

But this really isn't a new concept. Basically every modern compiled language works fine without needing separate header files.

by thayne

1/12/2025 at 4:47:16 AM

> This is trivial if your build system detects changes based on content (like, say, bazel), but if it uses timestamps (like make) then the compiler needs to ensure the timestamp isn't updated when the declarations don't change.

This is where the traditional distinction of "compiler vs Make" makes things harder; you want dependencies tracked at the "declaration" level, rather than the file level. If the timestamp _and_ content of the exported declarations file change, but none of the _used_ declarations changed, then there's no more compilation to be done. At best with file level tracking your build system will invoke the compiler for every downstream dependency, and they can decide if there's any more work to be done.

The build system would need to know which declarations are used (and what a declaration is) to do better.

by dwattttt

1/12/2025 at 6:53:41 PM

The D compiler has an option to generate a "header file" from D modules. It's called a .di file. It's useful if you want to hide the implementation from a compiler, as you would with libraries.

As it turned out, though, people just found it too convenient to just import the .d file.

But as a very unexpected dividend, it was discovered that the D compiler would generate .di files from compiling .c files, and realized that D had an inherent ability to translate C code to D code!!!! This has become rather popular.

by WalterBright

1/12/2025 at 3:40:14 PM

Nice explanation. Modules are the way forward. Looks to always have been. Not understanding the resistance, when the advantages are clear.

by baranul

1/12/2025 at 6:47:52 PM

I do understand the resistance. C is a simple, comfortable language, and its adherents want it to stay that way, warts and all.

But in the context of that, what baffles me is the additions to the C Standard, such as useless (but complicated!) things like normalized Unicode identifiers, things with very marginal utility like generic functions, etc. Why those and not forward declarations?

by WalterBright

1/12/2025 at 12:18:56 AM

Can't you use precompiled headers?

by daymanstep

1/12/2025 at 12:26:00 AM

Interesting you brought that up. I implemented them for Symantec C and C++ back in the 90s.

I never want to do that again!

They are brittle and a maintenance nightmare. They did speed up compilations, though, but did not provide any semantic advantage.

With D I focused on fast compilation so much that precompiled headers didn't offer enough speedup to make them worth the agony.

by WalterBright

1/12/2025 at 1:11:43 AM

>They are brittle and a maintenance nightmare

I happened to be reading DMC source this week, those hydrate/dehydrate stuff really is everywhere (which I assume is solely used for precompiled headers?)

by fuhsnn

1/12/2025 at 2:42:53 AM

Yup. I spent a crazy amount of time debugging that. The tiniest mistake was a big problem to find.

by WalterBright

1/12/2025 at 7:09:51 AM

I had an intern try to use precompiled headers for the Linux kernel. The road block they found was that the command line parameters used to compile the header must exactly match for all translation units which it is used. This is no the case for the Linux kernel. We could compile the header multiple times, but the build complexity was not something we could overcome during the course of one internship.

by ndesaulniers

1/12/2025 at 6:55:53 PM

> must exactly match

Yup. My compiler kept a list of which switches would perturb compilation and so would invalidate the precompiled header, and which did not.

Precompiled headers are an awful, desperate feature. Good riddance.

by WalterBright

1/12/2025 at 12:10:11 AM

I personally don’t like forward referencing because it makes code harder to read. You can no longer rely on the dependency graph being in topological order.

by xigoi

1/12/2025 at 12:14:11 AM

As the article writes, that forces the private leaf functions to be at the top, with the public interface at the end of the file. The normal way is the public interface at the top, and the implementation "below the fold", so to speak.

> topological order

You are correct. But its the reverse topological order, which is not the most readable ordering. One doesn't read a newspaper article starting at the bottom.

by WalterBright

1/12/2025 at 12:18:51 AM

Maybe it’s because I’m primarily a mathematician, but I like building complex stuff up from primitives and having the most important results at the end.

by xigoi

1/12/2025 at 12:56:28 AM

That's not how I do things in math. I always need motivation first. So I start with the theorem, look at a couple of examples to see why this theorem is interesting, and then the various lemmas leading into the proof. So that means I really like declaring but not defining the public interface first, and then define the private helper functions, and finally definitions for the public interface.

by kccqzy

1/12/2025 at 12:22:34 AM

Vive la différence - and you'll still be able to do it your way!

by WalterBright

1/12/2025 at 4:23:04 AM

Perhaps the difference is having algorithm in your head and just putting it into code, versus only knowing the top level work to be done and implementing the needed operations later.

If I am writing some kind of service, I would write the main public functions first, using undefined functions in their bodies as needed. Then I would implement those functions below.

by Lvl999Noob

1/12/2025 at 5:19:41 PM

I think you mean it in the context of proofs, right? Proofs are indeed often best written in a topological order: a series of true statements, where every reference refer backwards.

You don't often see

    Answer = A + B, 
    where 
    
    A = ...
    ...
    B = ...
albeit you sometimes see it, and it is totally valid. For proofreading something, it makes a big difference: if things are in a topological order, you can simulate a constant memory finite machine. If they are not in a topological order, well, probably you better just rewrite it (or at least I do).

For most other things, I usually prefer the bird-view first, when I am doing or reading some elses math.

Funnily the language Haskell which operates on definitions, is very order independent, it even allows circular definitions. I like it for leetcode and such.

by bmacho

1/12/2025 at 8:50:11 AM

People learn the ordering. If that is their biggest hurdle learning C they have a blessed life

by chikere232

1/12/2025 at 1:42:20 AM

Every other language does seems to not require header file/forward declarations. I don't understand the backlash against that.

Are modern C compilers actually still single pass?

by billfruit

1/12/2025 at 2:45:15 AM

> Are modern C compilers actually still single pass?

All except ImportC, which effortlessly handles forward references. (Mainly because ImportC hijacks the D front end to do the semantics.)

by WalterBright

1/12/2025 at 4:21:37 AM

A bit of an aside but I was poking around in the SPIR-V spec yesterday and they can do forward references because the call site contains all the information to determine the function parameter types. Just thought it was interesting and not really something I had thought about before.

by UncleEntity

1/12/2025 at 1:03:13 AM

> Evaluating Constant Expressions

The examples are quite simple in the article but I believe more complex cases would significantly degrade the compiler speed (and probably the memory footprint as well) and would require a VM to leverage this.

Which is probably assumed "too complex" to go into the standard. I'm not saying it's impossible, but I kind of understand why this would not go into any kind of standard.

> Importing Declarations

I wish C++ (or even C) would have gone into this direction instead the weird mess of what is defined for C++20.

Additionally you might import module into some symbol, like:

  #import "string.c" as str
and every non-static symbols from the file can be accessed from like:

  str.trim(" Hello World ");
> __import dex;

This is totally tangential but I don't like when file paths are not explicit. In this specific case I don't know if I'm importing dex.d or dex.c.

by kreco

1/12/2025 at 2:41:44 AM

> I kind of understand why this would not go into any kind of standard.

Other popular languages can do it. That aside, it is an immensely popular and useful feature in D.

And yes, as one would expect, the more it is used, there's compile time speed and memory consumption required. As for a VM, the constant folder is already a VM. This just extends it to be able to handle function calls. C has simple semantics, so it's not that bad.

> Additionally

Great minds think alike! Your suggestions are just what D imports do. https://dlang.org/spec/module.html#import-declaration

> In this specific case I don't know if I'm importing dex.d or dex.c

This issue does come up. The answer is setting up your import path. It's analogous to the C compiler include path.

by WalterBright

1/12/2025 at 4:57:27 AM

> I believe more complex cases would significantly degrade the compiler speed (and probably the memory footprint as well) and would require a VM to leverage this.

I'm pretty sure most production grade c compilers already do some level of compiler time evaluation for optimization. And C already has constant expressions.

I think a bigger hurdle would be that the compiler needs access to the source code of the function, so it would probably be restricted to functions in the same translation unit.

And then there is the possibly even bigger people problem of getting a committee with representives from multiple compilers to agree on the semantics of such constant evaluation.

by thayne

1/12/2025 at 3:53:57 AM

> The examples are quite simple in the article but I believe more complex cases would significantly degrade the compiler speed (and probably the memory footprint as well) and would require a VM to leverage this.

> Which is probably assumed "too complex" to go into the standard. I'm not saying it's impossible, but I kind of understand why this would not go into any kind of standard.

I mean, it's basically 1:1 with the constexpr feature in C++. Almost every C compiler is already a C++ compiler, supporting constexpr functions and evaluation in C can't be that bad, can it?

by loeg

1/12/2025 at 4:26:39 AM

I write unit tests for my C code all that time. It's not difficult if you use a good build system and if you are willing to stomach some boilerplate. Here is one test from my "test suite" for my npy library:

    void
    test_load_uint8() {
        npy_arr *arr = npy_load("tests/npy/uint8.npy");
        assert(arr->n_dims == 1);
        assert(arr->dims[0] == 100);
        assert(arr->type == 'u');
        npy_free(arr);
    }
    int
    main(int argc, char *argv[]) {
        PRINT_RUN(test_load_uint8);
        ...
    }
I know I could have some pre-processor generate parts of the tests, but I prefer to KISS.

by bjourne

1/12/2025 at 4:59:47 AM

Your function looks like it's doing I/O, which won't work at compile time test. Here's an example of a unittest for the ImportC compiler:

    struct S22079
    {
        int a, b, c;
    };

    _Static_assert(sizeof(struct S22079){1,2,3} == sizeof(int)*3, "ok");
    _Static_assert(sizeof(struct S22079){1,2,3}.a == sizeof(int), "ok");
The semantics are checked at compile time, so no need to link & run. With the large volume of tests, this speeds things up considerably. The faster the test suite runs, the more productive I am.

by WalterBright

1/12/2025 at 11:42:32 AM

Hey Walter, importC is great but on Mac it doesn't work right now because Apple seems to have added the type Float16 to math.h (probably due to this: https://developer.apple.com/documentation/swift/float16) and DMD breaks on that.

Could you have a look at fixing that?

by brabel

1/12/2025 at 6:22:18 PM

Aargh. Those sorts of extensions should not be in the system .h file.

by WalterBright

1/12/2025 at 3:46:19 PM

It is difficult to imagine that compile-time interpretation of tests is faster than compiling and running them for anything more complex. And for trivial stuff it should not matter. Not being able to do I/O is a limitation not a feature.

by uecker

1/12/2025 at 6:25:37 PM

Linkers are slow and clunky. Yes, there is a crossover point where executable tests are faster.

by WalterBright

1/12/2025 at 7:16:55 PM

For one of my C projects (ca. 450 c-files), full rebuild time on my (not super fast) laptop is just below 10 seconds (incremental builds < 1s). Compiling and linking all unit tests takes a second, and running all unit tests takes 6-7 seconds. So even running the optimized code for the tests almost doubles the time for rebuilding the full project. Although I like the machine code to be tested that is actually used. (BTW: A single C++ file with templates for CUDA someone added to the project - when activated - almost doubles the build time.)

by uecker

1/12/2025 at 7:58:55 PM

What about putting common template parameters into an external template in a binary libray?

by pjmlp

1/12/2025 at 11:13:36 PM

Na, we will just remove this template nonsense.

by uecker

1/13/2025 at 7:17:54 AM

I guess preprocessor macros are much better alternative. /s

by pjmlp

1/13/2025 at 7:37:48 AM

Probably yes. Macros are about equally bad than templates in my experience (although C++ people will disagree, I do not see much difference). But mostly I plan to just let the compiler specialize the functions during optimization. I haven't looked at this specific problem though.

by uecker

1/13/2025 at 8:12:11 AM

For one, templates can be stepped through on the debugger without additional effort, offer type checking, don't execute parameters multiple times, and don't require additional parenthesis and curly braces to protect their misuse.

by pjmlp

1/13/2025 at 10:20:32 AM

Debuggers can expand macros, and you can also look at the pre-processed output or even compile that (you can't the expanded form with templates). But I agree that if it becomes more complicated this is not very good. But you know, this does not matter too much to me as I will neither use complicated macros nor templates. And simple macros are just fine.

by uecker

1/12/2025 at 7:29:49 PM

This works in regular C as sizeof() is a constant expression, but perhaps that was your point?

by chikere232

1/12/2025 at 4:45:25 AM

You will be pleased to know that you are not the only one who does this.

I previously went down the rabbit hole of fancy unit test frameworks, and after a while I realised that they didn't really win much and settled on something almost identical to what you have (my PRINT_RUN macro has a different name, and requires the () to be passed in - and I only ever write it if the time to run all the tests is more than a second or so, just to make it really convenient to point the finger of blame).

The thing that I do which are potentially looked upon poorly by other people are:

1) I will happily #include a .c file that is being unit tested so I can call static functions in it (I will only #include a single .c file)

2) I do a tiny preprocessor dance before I #include <assert.h> to make sure NDEBUG is not defined (in case someone builds in a "release mode" which disables asserts)

by TheNewAndy

1/12/2025 at 10:37:10 AM

This test/src separation always felt like html/css to me. When still using C, I wrote tests right after a function as a static function with “test_” in the name. And one big run-all-tests function at the end. All you have to do then is to include a c file and call this function. Why would I ever want to separate a test from its subject is a puzzling thought. Would be nice to have “testing {}” sections in other languages too, but in C you can get away with DCE, worst case #ifdef TESTING.

by wruza

1/12/2025 at 3:18:06 PM

Because tests also serve as api validations. If you can't write a test for functionality without fiddling with internal details the api is probably flawed. Separation forces access via the api.

by bjourne

1/12/2025 at 7:53:33 PM

I don’t need anything to be “forced” on me, especially when this forcing is nominal and I can #include implementation details. You may need that in teams with absurdly inattentive or stubborn members, but for you-personally it’s enough to get the principle and decide when to follow it. The idea is to simply keep tests close to the definitions because that’s where the breaking changes happen.

If you can't write a test for functionality without fiddling with internal details the api is probably flawed

This logic is flawed. If you have an isolated implementation for some procedure that your api invokes in multiple places (or simply abstracted it out for clarity and SoC), it’s perfectly reasonable to test it separately even if it isn’t officially public.

by wruza

1/12/2025 at 3:43:43 PM

I agree with all of the above. The only fancy thing which I added is a work queue with multiple threads. There really isn't any pressing need for it since natively compiled tests are very fast anyway, but I'm addicted to optimizing build times.

by PhilipRoman

1/12/2025 at 6:05:00 PM

I really agree, I think that making the tests as easy as possible to get going goes a long way towards a code base that actually has tests.

I have something very similar.

https://github.com/ensisoft/detonator/blob/master/base/test_...

Borrowed heavily from boost.test.minimal and used to be a single header but but over the years I've had to add a single translation unit!

My takeaway is that if you keep your code base in a condition where tests are always passing you need much less complications in your testing tools and their error reporting and fault tolerance etc. !

by samiv

1/12/2025 at 4:46:43 AM

Compile time unit tests are as bad of an idea as "unused import/variable/result" errors (rather than warnings). They're "nanny features" that take control away from the developer and inevitably cause you to jump through bureaucratic hoops just to get your work done.

These kinds of build-failing tests are great for your "I think I'm finished now" build, but not for your "I'm in the middle of something" builds (which are what 99% of your builds are).

It's like saying "You can't use the table saw until you put the drill away!"

by kstenerud

1/12/2025 at 5:41:21 AM

> These kinds of build-failing tests are great for your "I think I'm finished now" build, but not for your "I'm in the middle of something" builds

i tend to disagree.

If you tried to express some thought but the compile time tests tells you you're wrong, you might actually just have an incomplete thought, or have not thought through all of the consequences of said expression.

It's basically what type-checking is in haskell - you cannot compile a program that does not type-check correctly. This forces you as a programmer, to always, and only, express complete thoughts. Incomplete, or contradictory thoughts cannot be expressed.

This should, in theory, lead to programs that are more well thought out. It also makes the program harder to write, because it forces the programmer to discover corners of their program for which they "know" isn't valid but don't care.

by chii

1/12/2025 at 6:22:06 AM

> it forces the programmer to discover corners of their program for which they "know" isn't valid but don't care.

And this is precisely why I disagree with forcing it upon the developer at every stage of development. Generally, while in the thick of things, I just want to get things working with one part, not worry about what other parts this breaks (yet). But the pedantic "you have to fix this first" enforcement breaks my concentration because now I have to split my attention to things I don't want to even be bothered with yet. I'll get to it, but I sure as hell don't want you telling me WHEN I should.

by kstenerud

1/12/2025 at 3:27:33 PM

> because now I have to split my attention to things I don't want to even be bothered with yet.

One of the reasons could that you realize you don't need those parts, so it would have been a waste of time to write tests for them.

Is that the same as saying I don't want to have to write types either? Maybe. Types are like lightweight incomplete specs.

by fishstock25

1/12/2025 at 5:49:20 PM

> One of the reasons could that you realize you don't need those parts, so it would have been a waste of time to write tests for them.

Or perhaps the parts existed, were useful, did have tests, and now a new feature requires refactoring that temporarily breaks things before I finally bring the house in order again. But I don't want to throw out the tests because parts of them may still be useful.

My point is, if the code is capable of being compiled and run, who is anyone to dictate that I shouldn't be allowed to run it (even broken) during my development cycle, just for some bureaucratic "I know better than you" reason?

This is the problem I see all over - people peer out from their limited perspective, assume that they see enough, and then make excessively restrictive policy decisions about what we can and cannot do. It's hubristic and so very, very annoying to the rest of us, especially since they also have a tendency to double-down, and there seems to be no way of getting through to them.

by kstenerud

1/12/2025 at 4:35:08 PM

> I don't want to even be bothered with yet

Why did you get out of your way to write tests about something that you don't want to be bothered about?

by marcosdumay

1/12/2025 at 5:39:56 PM

Because the test already exists as part of the solution that's worked up until now, and I don't want to modify it or any other related tests until I've finished my work to the point that I think the interface is stable enough for the tests to be updated.

by kstenerud

1/12/2025 at 7:43:16 AM

Test-driven development has its uses. But it is wrong to make it mandatory. I myself run static checks/unit tests almost all of the time. Still it is useful to skip them from time to time and just run the code to see the results (make it work before you make it "right" according to some linters rules).

by d0mine

1/12/2025 at 5:25:50 AM

Maybe these compile time tests are more like `static_assert`, which is valuable for catching incompatible uses of library functions. Pretty good idea in my opinion.

by omoikane

1/12/2025 at 5:33:43 AM

Sure, enforcing invariants is a good thing to do right off the bat. But not "does this code do what it says on the tin?" kinds of tests. Those are better run gradually, at the (current) developer's behest (and most certainly, not blocking compilation).

by kstenerud

1/12/2025 at 9:41:37 AM

The article is literally talking about `_Static_assert`, yes. It's used in the code examples and described in the text.

by tczMUFlmoNk

1/12/2025 at 10:59:05 AM

> The leaf functions come first, and the global interface functions are last.

To me that is backwards. I prefer code written in a topological order for a number of reasons:

- It mirrors how you write code within a function.

- It's obvious where you should put that function in the module.

- Most importantly, it makes circular dependencies between pieces of code in a module really obvious.

I'm generally not a fan of circular dependencies, because they make codebases much more entangled and prevent you from being able to understand a module as a contained unit. In Python they can even lead to problems you won't see until you run the code[0], but circular imports are probably so common that current type checkers disable that diagnostic by default[1].

I think languages that don't support forward references (C, but also OCaml and SML) let me apply the "principle of least surprise" to circular dependencies. OCaml even disallows recursive dependencies between functions unless you declare the functions with "let rec fn1 = .. and fn2 = ..", which may be a bit annoying while you're writing the code but it's important information when you're reading it.

[0]: https://gist.github.com/Mark24Code/2073470277437f2241033c200...

[1]: https://microsoft.github.io/pyright/#/configuration?id=type-... (see reportImportCycles)

by steinuil

1/12/2025 at 3:34:53 AM

> the compiler only knows about what lexically precedes it. […] it drives programmers to lay out the declarations backwards. The leaf functions come first, and the global interface functions are last. It’s like reading a newspaper article from the bottom up. It makes no sense.

Defining functions on a “bottom-up” order like this is common even in languages like Python which allow forward references. [0]

Is that just a holdover from languages which don’t allow such references? Or does it actually make more sense for certain types of code?

[0] https://stackoverflow.com/a/73131538

by pansa2

1/12/2025 at 3:57:22 AM

You're confused. There are no forward references in python. It's simply that identifiers in the body of a function aren't resolved until the function itself is executed (if ever) and at the point everything in the module scope has been defined. You can test this yourself by just putting any name into a body and loading the module.

by almostgotcaught

1/12/2025 at 4:21:07 AM

> identifiers in the body of a function aren't resolved until the function itself is executed (if ever) and at the point everything in the module scope has been defined

Yes, so within a function you can refer to things that are defined later in the module. Isn't that's a "forward reference", even if the details are slightly different from how they work in D?

by pansa2

1/12/2025 at 4:24:44 AM

[flagged]

by almostgotcaught

1/12/2025 at 4:48:55 AM

You're right, it's not the same mechanism. But in this particular context it has the same effect - it allows you to define functions in a source file in arbitrary order.

by pansa2

1/12/2025 at 8:32:13 AM

In my code, the public interface always tends to bubble upwards, and implementation details go at the end of the file. I don’t even have a strict rule for this, it just reads more cleanly and seems to make sense. Especially if the implementation is large - you don’t want to scroll through all that before you get to the basics of what it all does. I’m curious how everyone else does things.

by throwuxiytayq

1/12/2025 at 10:22:31 PM

> Everywhere a C constant-expression appears in the C grammar the compiler should be able to execute functions at compile time, too, as long as the functions do not do things like I/O, access mutable global variables, make system calls, etc.

That one is easily broken. Pick a function that runs for a lloooonngg time...

  int busybeaver(int n) {...}    // pure function returns max lifetime of n state busy beaver machine

  int x = busybeaver(99);

by drpixie

1/12/2025 at 4:35:38 AM

Some of my "obvious things c should do" for me would include things like

- add support for a slice type that encodes a pointer and length

- make re-entrant and ideally threadsafe APIs for things that currently use global state (including environment variables).

- standardize something like defer in go and zig, or gcc's cleanup attribute

- Maybe some portable support for unicode and utf-8.

by thayne

1/12/2025 at 5:52:11 AM

Aren’t most of these things you want in the standard library, and not things that the language itself should do?

by zffr

1/12/2025 at 6:01:20 AM

Only half of them. The first and third are language things.

The first could almost be done with macros. Except that separate declarations of an equivalent struct are considered different, so the best you cand do is a macro you can use define your owne typedef for a specific slice type. It could be done in the library if c supported something like a struct that had structural instead of nominal typing.

by thayne

1/12/2025 at 9:42:16 AM

I feel that much of the point of C is that it's easy to implement. Substantially increasing its scope doesn't seem like the best idea. Perhaps they could do something akin to Scheme and have a "small" and "large" version of the specification.

by James_K

1/13/2025 at 10:22:16 AM

That is long gone, when looking at C23 and the myriad of compiler extensions.

by pjmlp

1/12/2025 at 5:07:54 PM

They have that, to some degree. The standard library is mostly optional. Also, a lot of things are 'implementation defined', so you could just not implement those. That leaves quite a small language core.

by oplaadpunt

1/12/2025 at 8:21:00 AM

I think the real question is why not everybody has already moved to D, if it is so much better and can do all the great things. The answer is that all these things have trade-offs, including implementation effort, changes in tooling, required training, backwards compatibility, etc. some of the features are also not universally seen as better (e.g. IMHO a language which requires forward declaration is better, I also like how headers work).

by uecker

1/12/2025 at 5:19:58 PM

You mean, of course, Zig and Rust, right?

Because D has a sizable runtime library and GC, which can be opted out of, but with very significant limitations, AFAICT.

by nine_k

1/12/2025 at 1:45:30 AM

Good suggestions, but also meh, e.g.: forward declaration requirement enables a single-pass compiler to emit code on-the-fly.

I have a much better list for things to add to C: Nothing. C isn't perfect, or nearly as good as it could be, but simply adding things onto C gets you C++.

Adjusting what sircmpwn says: in C you don't solve problems by adding features, but by writing more code in C.

I liked an answer on stack overflow on a question on "how to write a generic function wrapper in Go", or something similar. Many suggestions included reflection, but the author wanted something simpler with varargs without reflection. A comment simply said: "wrong language".

I'd rather adopt this position for some languages, instead of add more and more to C3X. I do away with things in C23, and don't want even more things added in to C.

Making a strech of OP's arguments: "look at all this cool things that C could do, and that D does!". Well, go on and use D, nothing wrong with that.

(BTW, I do write test targets for every file in my C projects, but I'm not so much into jogging).

Those things aren't that obvious, and I'd rather not have them added to C.

Wrong language.

by EuAndreh

1/12/2025 at 2:21:08 AM

> forward declaration requirement enables a single-pass compiler to emit code on-the-fly.

True, I know all about that. My Zortech C and C++ compiler was one pass (after the multiple preprocessing passes). The ground up ImportC C compiler completed a couple years ago has a separate parse pass.

So I well know the tradeoffs. The parser being stand-alone means it is much simpler to understand and unittest. I found no advantage to a single pass compiler. It isn't any faster.

> simply adding things onto C gets you C++

C++ doesn't allow forward declarations either.

Successfully doing a parse-only on C code doesn't quite work. It turns out the grammar relies on a symbol table. Fortunately, only a symbol table of the typedefs. Once adding that in, ImportC worked. (I really tried to make it work without the typedef symbol table!)

C++ added a bunch more syntax that relies on the symbol table. I would not even try fixing it to work as parse-only.

> in C you don't solve problems by adding features, but by writing more code in C

The trouble with such sayings is like following a google map that says cross this bridge, but wasn't updated with news that the bridge is out.

> Those things aren't that obvious,

They are once you use another language that doesn't have those restrictions.

> and I'd rather not have them added to C.

C adds new things all the time to the Standard, like normalized Unicode identifiers, which are a complete waste of time. Every C compiler also adds a boatload of extensions, some good, some wacky, many ineptly documented, all incompatible with every other C compiler extensions.

by WalterBright

1/12/2025 at 4:00:29 AM

> The parser being stand-alone means it is much simpler to understand and unittest.

Stand-aloneness and single-passness are orthogonal.

> I found no advantage to a single pass compiler. It isn't any faster.

A gigantic advantage: a single-pass-compilable language is simpler. By definition.

Implementations may or may not be simpler or faster.

> C++ doesn't allow forward declarations either.

Well, that's not what I meant.

C++ is "C with just this thing" done way too many times.

> The trouble with such sayings is like following a google map that says cross this bridge, but wasn't updated with news that the bridge is out.

TBH, I didn't really get this. Is this about sticking to C as is, but it is outdated as is?

C would be outdated if it didn't have, say, long long for 64-bit numbers. Having "true" be a keyword instead of a macro doesn't change how outdated it is or isn't, just like compile-time evaluation also doesn't.

> They are once you use another language that doesn't have those restrictions.

I have used many, and I still don't find them obvious.

> C adds new things all the time to the Standard, like normalized Unicode identifiers, which are a complete waste of time.

I agree that many/most are a waste of time, and shouldn't be added to C. The fact of C adding things to the standard all the time shouldn't justify adding even more things, but make one question if those are needed at all, and how to accomplish the goal without it.

> Every C compiler also adds a boatload of extensions, some good, some wacky, many ineptly documented, all incompatible with every other C compiler extensions.

I know about that, and my position is the same: just don't.

I don't use them also.

by EuAndreh

1/12/2025 at 7:47:28 AM

> A gigantic advantage: a single-pass-compilable language is simpler. By definition.

That's only "by definition" if you take a language that needs multiple passes, then remove the features that need multiple passes, and don't replace them with anything else to compensate.

The "by definition simpler" version of C would not only disallow forward references, it would have no forward declarations either. As-is, forward declarations add some complexity of their own.

(Also, if you can figure out a way to emit jump instructions in a single pass, you can probably figure out a way to call unknown functions in a single pass.)

by Dylan16807

1/12/2025 at 6:36:18 PM

Doing jump instructions in a single pass is done by creating a patch list, and when the compilation is done walking the patch list and "fixing them up".

Doing this with functions is a lot more difficult, because one cannot anticipate the argument types and return types, which downstream influence the code generation. Of course, early C would just assume such forward references had integer arguments and integer types, but that has long since fallen by the wayside.

by WalterBright

1/12/2025 at 6:31:52 PM

I have the impression you're mixing single-pass compilation and O(1) memory use of the compiler.

As is, C already is single-pass compilable, modulo some unnecessary syntax ambiguities.

As the compiler reads the text, it marks some character strings as tokens, these tokens are grouped as a fragment of code, and some fragments of code are turned into machine code. A simple function of a 100 lines doesn't need to be parsed until the end for the compiler to start emitting machine code.

Like the parser, this requires memory to keep tabs of information and doesn't work for all types of constructs, like a jump instruction to a label defined later in a function. The code emitter soaks input untill it is possible, and does so, like when the label is already known and can be jumped to.

by EuAndreh

1/12/2025 at 6:40:32 PM

You cannot do any optimization when generating machine code that way. That's fine for a primitive compiler built for a school project, but not much else. (Even "no optimization" switch settings on a compile do a lot of optimizations, because otherwise the code quality is execrable.)

by WalterBright

1/12/2025 at 9:54:39 PM

> That's fine for a primitive compiler built for a school project, but not much else.

Not true.

On the one hand, just see how many non-compiled languages are used outside of primitive school projects.

On the other hand, this simpler approach is actually faster for writing actually fqst compilers. Many modern compiled languages have compilers that work on the order of ~100ms on a simple file with 1k LoC, when it could (and arguably should) work on the order of ~1ms, IOW, imperceptible given the syscalls overhead.

A 100x faster compiler that generates meh code is more useful 99% of the time: when one is recompiling all the time during development.

by EuAndreh

1/12/2025 at 1:51:52 AM

I have my own list of things that could "easily" be added to C, but I'd rather them not to be.

by EuAndreh

1/12/2025 at 2:21:32 AM

You get them anyway in the form of extensions.

by WalterBright

1/12/2025 at 4:05:32 AM

Thanks, but no thanks.

by EuAndreh

1/12/2025 at 6:58:46 AM

Maybe C should do that, but it won't. So why complain? Just work with the language as it is

by dailykoder

1/12/2025 at 4:39:51 AM

while this is a very interesting take, I think the premise is a little bit to simple.

Firstly, there are at least 3 C compilers in widespread use, from apple, microsoft and gnu, these are a long way from 1 for 1 to each other so when it says for example:

->In other words, while C can compute at compile time a simple expression by constant folding, it cannot execute a function at compile time.

Maybe the compiler he tried cannot, but another can, no idea, it wasnt tested, they can be made to (the whole point of the article), apple and microsoft cannot be made to, everything in this article could have have been submitted as a merge request to gnu.... Article doesnt even state whose C compiler they embedded afaict.

wrt to "standard" c specifically, there are for sure some hard constraints on all the wild and wacky hardware support required that must make proposing and implementing changes within the c standard extremely hard, max respect to the anonymous experts that have got it to where it is today, but imho a lot of the "why doesnt c" questions can be as easily answered as "why doesnt V8 support 8 bit pic micros".

by DarkmSparks

1/11/2025 at 11:41:17 PM

C++ has all of these except forward referencing declarations(though Importing Declarations requires modules which nobody uses yet).

I'm not sure why forward reference declarations is needed nowadays(or if it really is from a language standpoint).

C could probably copy C++'s constexpr & static_assert stuff to get the first 2.

by TinkersW

1/12/2025 at 12:11:27 AM

Nobody uses C++ modules because they are clumsy to use. D's are easy. Note: anyone is free to copy D's module design. It's the best one out there.

> I'm not sure why forward reference declarations is needed nowadays

The article gives reasons. Although they aren't necessary, they are deleterious to code layout which becomes a slave to the declaration order rather than aesthetic order.

> C++'s constexpr

is still lagging behind D's, after 17 years of development. In D, the garbage collector makes memory allocation in it trivial. Furthermore, only the path taken through a function needs to be CTFE-compatible, the path not taken does not.

by WalterBright

1/12/2025 at 4:34:27 AM

Robert Frost would approve

by zeroonetwothree

1/12/2025 at 12:56:34 AM

If I'm not mistaken, the treatment of forward declarations proposed in the article actually breaks the C standard, which would be a rather pressing concern. As far as I am aware, that is the reason why things are the way they are right now in C land.

(In the past, there were more legitimate concerns on the ease of implementation. Nowadays, as the article points out, they are pretty moot, other than having to keep backwards-compatibility.)

I'm also rather bothered that on the bit on const execution in the article, there was no discussion on how to deal with functions that may not terminate or take rather long to execute. Especially considering the unit tests motivation, this seems like a rather blaring omission.

by dccsillag

1/12/2025 at 2:29:35 AM

How does it break the C Standard?

> how to deal with functions that may not terminate or take rather long to execute

Control-C, the same as when running any executable that shouldn't be taking that long. It doesn't solve the halting problem :-/

by WalterBright

1/12/2025 at 4:25:44 AM

So a single typo DDoSes the entire Red Hat buildbot fleet?

by UncleEntity

1/12/2025 at 4:40:14 AM

You set a timeout, like in any robust CI.

by kstenerud

1/12/2025 at 3:57:51 AM

C's already got static_assert; just missing constexpr.

by loeg

1/13/2025 at 10:24:22 AM

Office team does use them.

There is Vulkan based modules library.

fmt has modules support.

Me and several others already use modules in some extent.

That is a bit more than nobody.

by pjmlp

1/13/2025 at 3:01:25 PM

The things I want most from a new C standard:

1. Fix the integer promotion rules. Obeying them as they are now makes code considerably less readable.

2. Choose one of the idiomatic approaches to type punning and make it standard. Having to use memcpy() over and over again is terrible.

3. Make casting a pointer-to-struct to a pointer to its first member supported. Stop leaving it to POSIX.

4. Make the exact-width integer types in stdint.h required. They were brazen enough to require support for long long in C99, so why not?

5. Make integer literals without type suffices be compile-time bignums.

by acuozzo

1/12/2025 at 4:27:49 AM

Woah I haven’t seen the name “digital mars” since the late 90s looking for compilers!

by matt3210

1/12/2025 at 3:33:19 PM

I consider forward references an anti-feature. I want any language I use to have the following property: if I append to the source file, I can't break previously correct code above the insertion point. Forward references both break this property _and_ requires multiple compiler passes.

More generally though, it's time to stick a fork in c. To me the only sane ways to use c are as a compilation target or for quick and dirty prototypes.

We can't make c better by adding to it. We need to let it die peacefully so its grandchildren may live.

by norir

1/13/2025 at 3:47:48 AM

What do you mean by "break previously correct code"? Can you give an example of what you're thinking with original code and appended code, where concatenating the two (1) does not change the behavior of the original code when forward references are not allowed, and (2) changes the behavior of the original code when forward references are allowed?

by me-vs-cat

1/12/2025 at 5:33:40 PM

C will always live.

I’m not onboard with significant changes to C but the language will always be around at the interface between hardware and software and probably as the lingua franca for FFI.

by the-grump

1/12/2025 at 4:26:56 AM

I'm not a c programmer, but having unit tests automatically run at compile time seems odd. If i wanted to run tests at the same time as compiling i would put that in the makefile.

by bawolff

1/12/2025 at 5:53:50 AM

Most of D users would rather call that "static contracts". I dont know why the author choose to call that "unit test".

by sixthDot

1/12/2025 at 4:28:33 AM

Why is it odd? The compiler does all sorts of other checks based on things like static assert or type information.

by zeroonetwothree

1/12/2025 at 4:34:42 AM

It's odd because you lose control over that aspect of compilation. It slows down the development loop because every time you do a build you have to wait for a bunch of unit tests that you don't care about yet.

Every time you do exploratory work you now have to comment out all the tests that this work breaks because otherwise it won't compile anymore.

That would be even more annoying than Go's stupidly pedantic compiler.

by kstenerud

1/12/2025 at 1:46:19 PM

> It slows down the development loop because every time you do a build you have to wait for a bunch of unit tests that you don't care about yet.

Can't that be an optional thing decided by some compiler flag? I think I remember doing something like that in D.

by Alifatisk

1/12/2025 at 2:08:13 PM

That's how one would normally do it, and that's what I would expect to see: Build for development, and the compiler only errors out if things are so bad that it's unable emit a binary (for everything else, it emits warnings). Build for release, and it errors out unless EVERYTHING is done right.

Unfortunately, ever since golang decided on an autocratic and backwards "there are no warnings, only errors" policy, others have started to sip from the same kool aid jar.

by kstenerud

1/13/2025 at 9:47:59 AM

I tried D last year. As I recall it, there's at least one obvious things from C that D should add:

Switches can't seem to figure out that I have a case for every value of the enum I'm switching on, so I need a pointless default: assert(0);

I didn't check if this also breaks me getting warnings if I add new values to the enum but not the switch, but I imagine it does

by chikere232

1/14/2025 at 8:15:07 AM

Obvious things C should do is not the same as things that C should obviously do. For instance you have to declare things in the right order in F# too and that seems to be mostly regarded as a modern language.

by ninalanyon

1/12/2025 at 5:59:46 AM

> Everywhere a C constant-expression appears in the C grammar the compiler should be able to execute functions at compile time, too, as long as the functions do not do things like I/O, access mutable global variables, make system calls, etc.

They have been working on bringing constexpr, which exists in c++, to c. This is essentially a constexpr function.

by nitwit005

1/12/2025 at 9:17:56 AM

If you want to write D, write D.

C is fine without these things

by chikere232

1/12/2025 at 12:28:48 PM

I want to write d, but I have a ton of c - d makes it easy. rust is harder as I have to write ffi. d makes working with something else easy shich it an advantage. Too bad it never took off.

by bluGill

1/12/2025 at 1:50:56 PM

> Too bad it never took off.

It may not be very hyped but the forum and community is quite active. I don't think it's popularity should stop you from exploring it, it's a fascinating language.

by Alifatisk

1/12/2025 at 6:43:15 PM

The obvious thing C should do since its syntax is already too rich and complex: create a µC or C- syntax profile which would remove tons of this complexity.

https://news.ycombinator.com/item?id=42657591

by sylware

1/12/2025 at 8:04:35 AM

The most obvious thing c should do is... evolve.

Even Fortran seems to have added object-oriented constructs, all kinds of new types and concurrent and parallel programming

by m463

1/12/2025 at 8:13:40 AM

There has been an object-oriented evolution of c, it's called c++ ;)

by 4gotunameagain

1/13/2025 at 11:50:28 AM

What? Nah C++ is some template based language. The real object-oriented evolution of C is Objective-C. ;)

by elcritch

1/12/2025 at 7:42:16 PM

It has and it does. Compare modern C with c89

It won't become D and you can probably be fairly sure it won't grow a standard garbage collector and object system.

by chikere232

1/12/2025 at 6:12:20 AM

Another obvious things C should do:

- better enums (with tagged union)

- compile time type introspection

D does the latter (very well btw), but completely missed the mark with enums

by WhereIsTheTruth

1/11/2025 at 11:18:31 PM

It really reads like the author just wants Zig.

by acheong08

1/11/2025 at 11:24:24 PM

The author is the creator of D, so he's probably fine with D. And D is something like 25 years old. Whereas Zig is just a toddler.

by robterrell

1/12/2025 at 4:35:31 AM

ahh, that explains the ImportC comparisons. Also didn't realize that person is also a regular on HN as well.

by johnnyanmac

1/11/2025 at 11:23:40 PM

Zig copies features from D!

by WalterBright

1/11/2025 at 11:30:40 PM

Imitation is the sincerest form of flattery.

by koolba

1/11/2025 at 11:31:50 PM

Yup. D is the source for a number of recent features in other languages.

The reason I embarked on D is because C and C++ were too reluctant to move forward.

by WalterBright

1/12/2025 at 5:23:21 AM

>The reason I embarked on D is because C and C++ were too reluctant to move forward.

I just want to take this opportunity to say thank you. While D may not taken up from the rest of the world. It has surely lived on in C, C++ and many other languages. Still wish more people would use Das C.

by ksec

1/12/2025 at 7:02:37 PM

Yes, the influence is there.

For example, C++ pivoted to using ranges instead of iterators, and even C# changed their iterators to be like D's ranges (or so I've been told).

by WalterBright

1/12/2025 at 1:16:44 AM

Do you think you will keep moving forward for the next decade or will you merge when c/cpp becomes similar enough to D ? Maybe your group still has tons of ideas that need their own space to grow.

by agumonkey

1/12/2025 at 2:02:23 AM

The ideas for advancing D come thick and fast. C and C++ will never merge with D, because we have different philosophies of what makes for a great programming language.

For example, D will never have a preprocessor. Or over my dead body :-/

by WalterBright

1/12/2025 at 2:19:23 AM

> For example, D will never have a preprocessor. Or over my dead body :-/

Indeed. Compile time evaluation combined with a preprocessor would make for some serious head scratching when it comes to trusting trust.

by koolba

1/12/2025 at 9:50:19 AM

Thanks a lot for your answer, I have another one, what languages or topics do you follow for inspiration regarding concepts ?

by agumonkey

1/12/2025 at 6:59:01 PM

I could spent 500% of my time studying other languages. So I don't, but the D user community isn't shy about doing that for me!

by WalterBright

1/12/2025 at 12:31:10 PM

C++ is trying to eliminate the use of preprocessor. they can't get rid of it but you shouldn't use it.

by bluGill

1/12/2025 at 6:57:56 PM

I see revision after revision of Standard C++, and no progress in that direction. I (along with Andrei Alexandrescu and Herb Sutter) made a proposal to add D's static if feature to eliminate the need for #if. It was vehemently rejected.

by WalterBright

1/12/2025 at 5:17:50 PM

Stroustrup has been saying that in print since 1993, and it hasn’t happened yet.

by KerrAvon

1/13/2025 at 2:01:58 PM

The biggest advance would be Microsoft adopt D.

by childintime

1/12/2025 at 6:16:02 AM

I'm curious, is there any features, from any language, that you wish you had implemented, or want to copy, in D?

by WhereIsTheTruth

1/12/2025 at 7:04:37 PM

There's active work going on to add pattern matching. Just recently I completed the addition of move constructors. They are different from C++'s, and I bet they'll turn out to be much better.

by WalterBright

1/12/2025 at 5:19:17 AM

Identity theft is not a joke, millions of families suffer every year!

Michael!

by ksec

1/12/2025 at 12:46:51 AM

more importantly, though, zig deliberately doesnt implement a whole TON of things that D does.

sometimes parsimony is called for. zig is basically c--+ where the + is the constexpr stuff.

by throwawaymaths

1/12/2025 at 12:49:47 AM

It keeps adding D features anyway, like constexpr.

by WalterBright

1/12/2025 at 12:51:22 AM

the constexpr stuff has been around since the beginning[0]. concretely, what other thing from D do you claim zig has added since?

[0] minimally 2019, 3 years in:

https://youtu.be/Gv2I7qTux7g?si=p0kVhtB56GvVLLNr

by throwawaymaths

1/12/2025 at 3:35:53 AM

D had compile time execution of functions in 2007.

Order-independent top level declarations.

Underscores embedded in integer literals. (I stole this idea from Ada, which had been forgotten. Soon after D popularized it, it became standard in other languages.)

Continue or break to labeled loop.

Fixed sizes for ints, longs, etc.

Of course, I don't know if there's a straight line here, and Zig is welcome to use any features from D that they like. But it's just interesting that things innovated in D pop up in subsequent designs.

by WalterBright

1/12/2025 at 1:56:22 PM

> Underscores embedded in integer literals.

Perl had this years before D even existed [1]. Given its earlier age and higher reach, it's likely that Perl did more to popularize the idea as well.

> Continue or break to labeled loop.

Also a feature that Perl had years before D's existence [2].

These two are the ones I immediately recognized because of my familiarity with Perl, but given the trend, I'm doubtful of the other claims as well now.

[1] https://perldoc.perl.org/5.005/perldata [2] https://perldoc.perl.org/5.005/perlsyn#Loop-Control

by sundarurfriend

1/12/2025 at 7:08:13 PM

I did not know Metaware and Perl did this, as I've never used either. I know that my inspiration for it came from Ada.

I accept that Metaware and Perl did this before D. But I still claim that adoption of it in other languages came shortly after I popularized it in D, as I included it in many presentations about it.

by WalterBright

1/12/2025 at 4:23:45 PM

Metaware High-C version 1.2 (Nov 1985) had underscores in floating point and integer literals. Possibly it had that even earlier.

Possibly also taken from Ada, as other text in that section of the manual reference Ada.

See A.3 pg 169 (and 58+) of 235 in: https://bitsavers.org/pdf/metaware/High_C_Language_Reference...

by dfawcus

1/12/2025 at 7:09:01 PM

I did not know that. Thanks for pointing it out.

by WalterBright

1/14/2025 at 9:11:54 AM

mind, it is also possible that they took that from Algol 68, and 'adjusted it':

  $ a68g --strict -e '(INT a = 1 000; print((a,newline)) )'
        +1000
As Algol 68 allows spaces within numbers, as well as within identifiers.

by dfawcus

1/13/2025 at 1:26:44 AM

> Order-independent top level declarations.

Javascript has had this for functions since 1995. This has been part of zig from the start, not added later.

> Fixed sizes for ints, longs, etc.

this has existed in stdint.h since C99. It doesn't take a genius, only years of pain with C/C++, to realize this is the better way to do things. And also, this was in zig from the start, not added later.

> Underscores embedded in integer literals

Also in zig from the start, not added later. Others have commented on the provenance.

I could be convinced that continue/break to labels was inspired by D.

by throwawaymaths

1/12/2025 at 4:36:26 AM

>> Continue or break to labeled loop.

Hasn't Java had that since the beginning?

rummaging around in my grammar folder...

BreakStatement ::= "break" [ IDENTIFIER ] ';'

ContinueStatement ::= "continue" [ IDENTIFIER ] ';'

by UncleEntity

1/12/2025 at 5:10:00 AM

And so it does. I had forgotten. Thanks for the reminder!

by WalterBright

1/12/2025 at 6:22:16 AM

Parsimony? more like thankful for learning from other language experiments and research work

Zig is young, and far from 1.0, and many long promised features still not implemented

by WhereIsTheTruth

1/12/2025 at 5:56:49 PM

> many long promised features still not implemented

name one that isn't async / sane recursion (which is also async)

by throwawaymaths

1/11/2025 at 11:22:45 PM

or maybe even D

by convolvatron

1/12/2025 at 12:36:07 PM

i think compile time evaluation should be extended: if it can get a pass over the source code then this could replace preprocessor macros with something less shitty.

by MichaelMoser123

1/12/2025 at 2:44:43 PM

maybe even add reflection and the ability to check for the type of a value - but that is probably too much to ask for.

by MichaelMoser123

1/12/2025 at 12:22:47 AM

Yeah... no.

Constexpr function evaluation sounds like a great idea until you start trying to use it, and get surprised when seemingly-constexpr-safe functions aren't constexpr. Or, you tweak one function and suddenly all your fancy compile-time unit tests explode.

Ok, so you get around that with good code hygiene and by limiting the complexity of your constexpr functions... in other words, do the exact things we already do with preprocessor macros.

Alternatively, you add a constexpr keyword to the lang, but now we have red functions and blue functions. Great.

In another language, there'd still be an argument for the type-safety that would precipitate from constexpr function eval, but this is C we're talking about.

How about container_of? Could we please standardize that already? Why is this crucial and immensely useful macro a thing we all copy-paste from that one page on kernel.org?

by ryukoposting

1/12/2025 at 3:00:31 AM

Just like constant expressions, a function run at compile time needs to be pure, which means no globals, no system calls, no I/O, no undefined behavior. Yes, that does constrain it somewhat, but D users have found it to be immensely useful anyway.

There's really no comparison with preprocessor macros. All the preprocessor can do is trivial expressions with long values. Not even floating point.

> you add a constexpr keyword to the lang, but now we have red functions and blue functions

My proposal (and D) does not require constexpr. The same function can be used at run or compile time. There is no need for that keyword. C++ made a mistake.

by WalterBright

1/12/2025 at 4:32:37 AM

> D users have found it to be immensely useful anyway.

I think you're focused on something that D programmers found helpful, rather than focusing squarely on the needs of C programmers. C and D are both good languages. Their use cases can overlap, but frequently don't.

>=70% of the code I write for work is C, as I'm an embedded firmware dev. C meets the very particular needs of bare-metal development, a use case that continues to be underserved by Rust, Zig, and other supposed successors to C. So, I'll be approaching this with a strong bias towards that perspective.

To me, the argument about unit testing rings hollow because of all the other, far more complicated and runtime-subverting things that would also need to become standardized before it would be feasible to unit test C programs without help from the hideous hacks we use today, like CMock. So, all of that isn't doing anything for me.

> My proposal (and D) does not require constexpr. The same function can be used at run or compile time.

Like you, I dislike the constexpr keyword. But, particularly considering your own example of defining an enum value, I don't see how the "implicit const-evaluatable" approach makes my life easier. Calling functions to define an enum's value is cute, but I can't tell you why I'd actually want to do that. You mention that the preprocessor can't handle floats, but, well, neither can enums!

Adding a single printf (or, in my case, kprintf or LOG_DBG or whatever) would become liable to nuke some constant evaluation happening somewhere far up an obscure call chain. The basic reality of C is that you will, at one point or another, encounter a situation where your only debugging tool is print statements (or a single LED). That's the cold reality of C's paper-thin runtime.

So, I really dislike the idea of having a feature that's liable to make your code go "boom" at compile time because you put a print statement in just the wrong spot. Or a write to a memory-mapped register. Or a hard jump into a blob sitting somewhere in memory. Or inline assembly. Or a call to a function that uses any of those things, even once. Even without the keyword, you end up with red functions and blue functions. It's just harder to tell which ones are which.

Defining some const floats? Sure, that's neat. If you could use this feature to define huge matrices of floats, that'd be pretty cool! There's just a boatload of gotchas.

by ryukoposting

1/12/2025 at 5:26:46 AM

> your own example of defining an enum value

That is hardly the only place that has a constant-expression in the grammar. (BTW, D enums can also be floats, and even string literals!) You could use CTFE to initialize const floating point globals. static_assert also takes a constant-expression.

> There's just a boatload of gotchas.

The D community has 17 years experience with it. It remains an indispensable feature.

As for embedding a printf, that has come up. Recall elsewhere I said that only the path through a function has to be pure for CTFE to work, not the whole function?

    int sum(int a, int b) {
        int s = a + b;
        if (!__ctfe) printf("sum is %d\n", s);
        return s;
    }
__ctfe is a keyword that says "CTFE is executing this function".

> If you could use this feature to define huge matrices of floats, that'd be pretty cool!

I use it to statically initialize complicated tables at compile time. Before CTFE, I wrote a separate executable that would emit source code with the array initializer. I like the new way mucho bettero.

If you prefer "hideous hacks" (your words!) I won't take that away from you.

by WalterBright

1/12/2025 at 9:05:39 AM

Not to take away from the possible usefulness of constant functions, but it's amusing that the article example of `sum(5,6)` would work great in C if written as 5 + 6, or done as the equivalent macro

by chikere232

1/12/2025 at 6:30:41 PM

Trivial examples make it easy to explain and understand the concept. Just like with Calculus, we don't start with integrating e^x. We start with integrating x.

by WalterBright

1/12/2025 at 3:07:57 AM

If you're using C++ 20, you can mark functions consteval to force constant evaluation.

by throw16180339

1/12/2025 at 7:41:20 AM

I wish C had a jinja2-like preprocessor!

by notorandit

1/12/2025 at 8:59:17 AM

You could use whatever preprocessor you like really, just add a make step that translates your template into c. I've seem some crazy person use M4 for example.

It's usually a bad idea, but very feasible

by chikere232

1/13/2025 at 6:44:21 AM

Integrated preprocessor. That was what I meant. Maybe with c-syntax awareness.

by notorandit

1/12/2025 at 1:12:18 AM

Not again, not another D thread noooooooooooooooooo (i'm joking)

by foul

1/12/2025 at 2:31:42 AM

All your base belong to D!

by WalterBright

1/14/2025 at 12:18:56 AM

[dead]

by bretcindy

1/12/2025 at 3:49:50 AM

[stub for offtopicness]

by dang

1/11/2025 at 11:25:53 PM

I didn't know X put articles behind a paywall? I haven't tried putting articles there before.

Anyhow, here's the same article:

https://www.digitalmars.com/articles/Cobvious.html

Fun fact: X's article formatter recognizes D code!

by WalterBright

1/12/2025 at 2:39:56 AM

Haven't you noticed that the homepage does not include the X widget anymore ? It's been removed [1] exactly because of that, i.e the content is not public anymore.

[1]: https://github.com/dlang/dlang.org/pull/3714

by sixthDot

1/12/2025 at 12:37:40 AM

I'm not sure what you are seeing, but perhaps it's just a login wall. I was able to read it; I'm logged in but have never paid for Twitter or X. X does tend to hide certain things (such as replies and replied-to tweets) if you're not logged in.

by wging

1/12/2025 at 2:30:56 AM

I'm already logged in, which I infer is why I didn't see a login wall. Anyhow, I also posted an alternate link.

by WalterBright

1/12/2025 at 1:46:23 AM

it's not visible to nitter, which is annoying.

by kurisufag

1/11/2025 at 11:13:50 PM

login-walled

by ranger_danger

1/12/2025 at 12:04:50 AM

Curious. I don't have an account, and I can read it.

by dwattttt

1/11/2025 at 11:25:05 PM

Have a go at this => https://t.co/KXRE5XvuoP

I used the https://publish.twitter.com thing against the Xeet, then lifted the first HREF out of the embed goo.

by smitty1e

1/11/2025 at 11:40:55 PM

Doesn't work, the t.co link gives me:

> This page is not supported. > Please visit the author’s profile on the latest version of X to view this content.

by nom

1/11/2025 at 11:55:31 PM

Worth a try.

by smitty1e

1/11/2025 at 11:16:34 PM

Looks like a new feature. Articles don't render render on xcancel

by acheong08

1/12/2025 at 1:33:15 AM

Perhaps I could load the Javascript and also "login to X", but I'll instead forego reading this and pay attention to what others are writing about C.

by honestSysAdmin

1/11/2025 at 11:22:52 PM

Sorry to be off-topic, but I'm disappointed in this being on X, it even comes with an ad built-in. We should be doing better. Spinning up a single-site blog is easier than ever for technically-minded folks - the internet needs to become independent again.

by meibo

1/12/2025 at 2:55:47 AM

I posted it originally on digitalmars.com, and nobody noticed it. I posted it today on X, and bang! I didn't even submit it to HackerNews.

I'll be using X more for articles in the future, but will also put them on the dlang.org and digitalmars.com sites.

by WalterBright