Obvious things C should do

digitalmars.com

239 points by LorenDB a day ago


TheNewAndy - 19 hours ago

Header files are one of the things I miss the most about languages that aren't C. Having a very clear distinction between public and private, and interface and implementation is one of my favourite things about C code (at least the way I write it).

Being able to just read through a library's .h files to know how to use it is really nice. Typically, my .h files don't really look like my .c files because all the documentation for how to use the thing lives in the .h file (and isn't duplicated in the .c file). It would be entirely possible to put this documentation into the .c file, but it makes reading the interface much less pleasant for someone using it.

drpixie - an hour ago

> 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);
chacham15 - a day ago

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?

bjourne - 19 hours ago

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.
kreco - a day ago

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

kstenerud - 19 hours ago

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!"

pansa2 - 20 hours ago

> 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

thayne - 19 hours ago

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.

James_K - 14 hours ago

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.

steinuil - 12 hours ago

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

pajko - 17 hours ago

C23 has constexpr, which cannot be given for functions yet, but there's a proposal: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2976.pdf

uecker - 15 hours ago

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

norir - 8 hours ago

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.

dailykoder - 16 hours ago

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

DarkmSparks - 19 hours ago

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

EuAndreh - a day ago

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.

matt3210 - 19 hours ago

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

TinkersW - a day ago

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.

chikere232 - 14 hours ago

If you want to write D, write D.

C is fine without these things

bawolff - 19 hours ago

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.

nitwit005 - 17 hours ago

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

m463 - 15 hours ago

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

MichaelMoser123 - 11 hours ago

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.

sylware - 5 hours ago

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

acheong08 - a day ago

It really reads like the author just wants Zig.

ryukoposting - a day ago

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?

notorandit - 16 hours ago

I wish C had a jinja2-like preprocessor!

WhereIsTheTruth - 17 hours ago

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

foul - a day ago

Not again, not another D thread noooooooooooooooooo (i'm joking)

dang - 20 hours ago

[stub for offtopicness]