The case against a C alternative
c3.handmade.network
c3.handmade.network
I don't think this is true, in the general case: Rust has shown that languages can be safe in ways that improve runtime performance.
In particular, languages like Rust allow programmers to express stronger compile-time constraints on runtime behavior, meaning that the compiler can safely omit bounds and other checks that an ordinary C program would require for safety. Similarly, Rust's (lack of) mutable aliasing opens up entire classes of optimizations that are extremely difficult on C programs (to the extent that Rust regularly exposes bugs in LLVM's alias analysis, due to a lack of exercise on C/C++ inputs).
Edit: Other examples include ergonomic static dispatch (Rust makes things like `foo: impl Trait` look dynamic, but they're really static under the hood) and the entire notion of a "zero-cost abstraction" (Rust's abstractions are no worse than their "as if" equivalent, meaning that the programmer is restricted in their ability to create suboptimal implementations).
For example, typical C programs also don't use hashtables even when this makes the most sense, causing weird performance cliffs due to O(n^2) algorithms all over the place. Why not hashtables? Because they're not generic, so they're a pain to use. Not impossible of course, it's just that C developers avoid them.
Similarly, "strongly typed" containers full of simple struct types enable compiler auto-vectorisation that's often unavailable in C for the same kind of reason.
Last but not least, you would have to be a masochist to write heavily multi-threaded code in C... so hardly anybody does. These days, that's throwing away over 90% of the computer power in a typical PC, let alone a server.
This was equally true back when C vs Fortran was the big debate, and something not easily captured in benchmarks. C, as written by an expert in high performance C, was equally fast as Fortran written by an expert in high performance Fortran. C, as written by a domain expert with limited programming skills, was often very much slower than Fortran written by a domain expert with limited programming skills.
The gist, as I recall it was: the initial, idiomatic, written-for-maintainability version of the C# program was significantly faster than the C++ equivalent. Up until the end, the C# version also generally needed to go through less heroics to keep up with the C++ version. Eventually, the final C++ version did end up being faster than the fastest C# one, but, considering what needed to be done to get there, it was a decidedly Pyrrhic victory.
One huge mitigating factor, though, is that the model program was doing something business-y. I doubt C++ would have had such a hard time of it if it had been a number crunching or systems program.
And many that are negated due to a bad std API.
You obviously ideally want both, but that's not actually possible when you have a language this powerful. So, Rest's choice means sometimes (more rarely these days but it can happen) you will write a program that is correct, but the compiler doesn't believe you and rejects your program, you will need to alter it, perhaps after alterations it's actually nicer, but equally perhaps you feel this made it uglier or slower, nevertheless you have no choice in Rust (well, you could try waiting a few years, the compiler gets smarter)
However the C++ choice means sometimes (maybe even often) you will write a program that isn't correct and the compiler gives you no indication whatsoever that there's a problem, you get an executable or object file or whatever out, but what it does is completely arbitrary. Maybe it works how you expected... until it doesn't.
The magic phrase in the C++ standard is "Ill-formed, no diagnostic required". For example suppose you try to sort some floats in C++ 20. That's ill-formed (floats aren't in fact Totally Ordered but the function signature says you promise they are) and no diagnostic is required for... whatever it is your program now does. Maybe it crashes, maybe it works fine, not their problem, good luck with that.
Now, probably if all your floats are like boring normal finite reals like -2.5 or something this will work fine, there's no practical reason it wouldn't, but who knows, the C++ language denies all responsibility. So it gets to be very "optimal" here since it can do whatever it wants and it's your fault.
Instead, to sort a slice of floats, you have to explicitly specify what would happen for the non-ordinary cases; e.g. by using `.sort_by(f32::total_cmp)`, where f32::total_cmp()[2] is one possible interpretation of a total ordering of floats. This requires writing more code even for cases where it would be completely unnecessary.
[1]: https://doc.rust-lang.org/std/primitive.slice.html#method.so... [2]: https://doc.rust-lang.org/std/primitive.f32.html#method.tota...
[0] https://capnproto.org/news/2015-03-02-security-advisory-and-...
struct Example<const N: usize> {
pub e: [i32; N],
}Has C++ ever failed to snarf a feature?
But part of the impetus for Carbon is that WG21 (the C++ Standards Committee) rejected proposals that C++ should focus on better performance and safety. So maybe performance is no longer important either. What's left?
Where they've taken things which might appear on the surface to be modelled on a safer Rust feature, usually the committee insists they be made unsafe. For example suppose I call a Rust function which might return a char or might not, it returns Option<char> and if I'm an idiot and I try to treat that as a char, it doesn't type check because it isn't one, I need to say what I'm going to do when it isn't or else that won't compile.
You can write that in modern C++... except it can automatically try to take the char (which isn't there) out of the empty optional structure and that's Undefined Behaviour. So whereas the Rust prevents programmers from making easy mistakes, the C++ turns those into unexploded bombs throughout your code.
One obvious corner case: It is very common to allocate a buffer at startup and let the system clean it up when the program exits. (often this is embedded cases where the only way for the program to exit is power off). I don't know how you do this in rust (if you can - I'm not a rust expert)
This is possible with the lazy_static library or (not yet in stable Rust) OnceCell. It allows you to allocate & initialize any datastructure once during runtime and get global read-only access.
And C++ has the potential to be faster than C, mostly thanks to metaprogramming (templates, ...). It is horrible if you have to do it, but if you are just using the standard library, you don't have to feel the pain but still take advantage of it. That's how algorithms are implemented. Because so much is known at compile time, optimizers can do a lot.
The reason C++ is generally regarded as slower is that C++ programmers tend to create objects on the heap all the time because constructors and destructors make it easy. Modern C++ also discourages raw pointers and so you get references counters all over the place, essentially turning C++ into a garbage collected language. I am not saying it is bad, but it certainly impacts performance.
But if you manage your memory in C++ just as you do in C, keeping track of all your buffers reusing them, and not using more than necessary, I can easily see C++ beat C.
This doesn't match my experience. It's true that modern C++ discourages owning raw pointers, but the solution is usually unique_ptr, not shared_ptr. Truly shared ownership is actually pretty uncommon IME, usually you can have one entity which obviously "owns" the object and then any other reference to it can be non-owning.
It's also worth noting that with std::move, actually changing the refcount of a share_ptr can be pretty rare even if you do have shared ownership.
Tbh, Back then, I didn't see a problem with it. Once i started chasing down weird bugs where objects aren't freed properly because no one knew which objects own what, I have been very cautious.
Since 1993, I never saw any need to keep bothering with C other than having it imposed on me, C++ had enough C89 subset on it, if I ever miss coding like C and its warts.
Nowadays that compatibility is up to C11 subset.
Not true unfortunately, the "C subset" is still stuck at something that can at best be called a fork of "C95" which was then developed into a "bastard language" that resembled C on the surface, but isn't actually C (e.g. the incomplete designated init support in C++20 is the best example of this half-assed "looks like C, but isn't actually C" philosophy).
On the other hand, empirically, it is not unusual to see straightforward C programs being dramatically faster than comparable C++ programs written in enterprise style, and to also build much faster.
> Last but not least, you would have to be a masochist to write heavily multi-threaded code in C
You have to be a masochist to write heavily multi-threaded code that uses a lot of ad-hoc synchronization with mutexes and atomics. As it turns out, for many many tasks, it's also a spectacular bad way to go about parallelization, because mutexes are the _opposite_ of parallelization.
As a rule of thumb, do coarse-grained concurrency. Install a few queues, come up with a job system, and it won't be hard to get parallization right in plain C at all. Writing in C is often a good idea because what's a bad idea to do on hardware coincedes pretty well with what is painful to write.
Your only comparison cases are cases where the code in question was re-written in C. This most likely means that everyone already knew it was slow and so the re-write also fixed the fundamental problems. If the code had been rewritten in C++ it would also be faster - and since C++ allows some optimizations C doesn't it would be even faster. (it is known that if you switch from gcc to g++ your code often will run faster if it compiles)
There is a reason for enterprise style C++. Most of the time it is still fast enough, and it is a lot more maintainable.
I've never heard such a claim, can you back it up? And what does it say about the language?
> and it is a lot more maintainable
If you equate "maintainable" = readable, I've never once seen maintainable enterprise code. Everything is a convoluted mess that never gets anything done. Probably I haven't worked at the best shops, but then again, where are those? And why doesn't the language help mediocre programmers to write maintainable code?
I suspect that maintainability is almost exclusively a function of experience, not the programming language used. Experienced programmers do seem to agree that C-style C++ or even plain C is the way to go.
IME that's mostly a myth though. A C compiler will stamp out a specialized version just as well if it can see all the relevant function bodies (either via inlining or LTO).
"Zero cost abstraction" isn't just a C++ thing, it happens mostly in the language agnostic optimizer passes. For instance the reason why std::sort() shows up faster in benchmarks than C's qsort() is simply because std::sort() implementation is all inline template code, not because of some magic performance-enhancing qualities of the C++ template system.
AFAIK out of the major compilers, gcc has the most aggressive cloning, but it's still nowhere near to const propagate the comparator from qsort. With std::sort with a stateless comparator function object (such as std::less, which is the default), you get this for free*.
* of course this is not entirely free, as this is more prone to code bloat. But, you can always type-erase the comparator, and use a function pointer, or std::function, if this ever becomes a problem. But you can't convince a C compiler to const propagate the comparator in qsort all the way through, if the optimizer chooses that it doesn't worth it.
It's also an apples-to-oranges comparison, since std::sort and qsort implement different algorithms.
A lot of std::sort's performance is actually from using the version without any callbacks. If you pass a comparator function which just compares two integers the obvious way, it gets much slower. So one of std::sort's biggest advantages is actually not that it uses templates, but that it's specialized for the common case of not needing a custom callback. Theoretically the compiler should make the two cases the same, but apparently GCC is too dumb (that's not a slight on GCC; I think people expect too much from compilers):
------------------------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------------------------
std_sort_random 52881299 ns 52873089 ns 14
std_sort_with_callback_random 63319633 ns 63307876 ns 11
qsort_random 106803314 ns 106784567 ns 7
external_sort_random 97642851 ns 97640888 ns 7
std_sort_sorted 8433311 ns 8432564 ns 82
std_sort_with_callback_sorted 13868016 ns 13865170 ns 50
qsort_sorted 28098439 ns 28093720 ns 26
external_sort_sorted 33629020 ns 33628108 ns 21
external_sort is just std::sort hidden behind an extern function implemented in a separate .o file. Those benchmarks are from sorting 1MB of random and already-sorted data (as indicated in the names). I think it's important to test such cases, because often online people benchmark code which is written all in a single file, whereas real-life C++ projects are usually organized in such a way that every little class is in its own little file, which gets compiled into a separated object file, and then it all gets linked together without LTO. And then those same people go on to claim performance benefits of their language without actually using the setup which enables those benefits, which IMO is a bit dishonest.When I drill further down into everything I want to drill into, maybe I'll publish the source for the benchmarks somewhere.
PS.: I'm just annoyed that my generic C hashtable that is written in a qsort style doesn't get function copied/inlined when it's used for more than one type.
That’s PCs that have steam installed at all.
Intel’s bare minimum current-gen i3 processor has 12 threads. That’s the absolute cheapest desktop-level processor you can get.
Your phone probably has 6 cores (though not 12 threads).
So yes, if you’re writing code for desktop hardware, it’s safe to assume you have at least 8 threads. Maybe you don’t want to consume all of them, but it’s better to let the OS handle scheduling.
If I look around me, for instance in my whole family we're two with Steam installed but ever household has a desktop or a laptop (and generally a 7-8 years old cheap entry-level 350€ one, you'd be hard-pressed to find even a quad-core in there)
Steam doesn't even measure publicize information about threads on the survey, which makes it near impossible to check because not that long ago Intel locked out hyperthreading/SMT on their low/mid-grade CPUs.
Additionally, and more importantly: the Steam hardware survey _obviously_ doesn't represent the average consumer PC.
I'm about to buy a PC with 16 cores and 32 threads for "normal" money.
The AMD EPYC server CPUs scale to dual sockets with 64-cores each, for a whopping 256 hardware threads in a single box. That's not some sort of esoteric hyper-scale configuration, but completely ordinary off-the-shelf stuff you can get in large quantities from mainstream vendors.
A single-threaded application on a server like that will use between 0.5% to about 50% of the total available performance, depending on where its bottleneck is. It will never reach 100%!
This matters to things like CLI tools, batch jobs, and the like, many of which are written in C, especially in the Linux world. A case-in-point that demonstrates how much performance has been left on the table is ripgrep, which is a multi-threaded Rust replacement for grep.
I think you are mixing hyperthreading, or SMT with regular "software" threading
Parallelism aka CPU-bound tasks are limited by the number of cores you have. Concurrency aka IO-bound tasks are not, because they're usually not all runnable at once. It can be faster to go concurrent even on a single core because you can overlap IOs, but it'll use more memory and other resources.
Also, "going faster" isn't always a good thing. If you're a low priority system task, you don't want to consume all the system resources because the user's apps might need them. Or the the user doesn't want the fans to turn on, or it's a passive cooled system that shouldn't get too hot, etc.
And for both of them, it not only makes it easier to write bugs in unsafe languages, but in safe languages you can easily accidentally make things slower instead of faster just because it's complicated.
I'm not sure it's fair to say C developers avoid hash tables - I've worked on several projects with hash-table implementations in them.
The 'problem' if there is one, is that such things are rarely picked up from any sort of standard library, and are instead implemented in each project.
I'm also not really sure what the problem is with 'resorting' to void*, it's part of the language. It's not 'safe' in that the compiler won't catch your errors if you stuff any old thing in there, but that's C.
> you would have to be a masochist to write heavily multi-threaded code in C
pthreads makes it relatively straightforward. I've seen (and written) fairly sophisticated thread-pool implementations around them.
Yeah C doesn't really go in for that sort of thing. The standard library tends to be much more about some minimal support for strings and interfaces to OS features like files, signals, memory allocation etc. It doesn't really provide much in the way of building blocks to be reused by application developers.
The recommendation out there on the net seems to be to look at Glib, which is used by gtk, for that sort of thing.
Another good alternative might be the NSPR - https://firefox-source-docs.mozilla.org/nspr/reference/index...
I used this way back in 2001-3 for a multi-platform project because it provides some good platform abstractions, and it looks like it has a hash-table implementation in amongst its other features.
Anything you can do in C++, you can do in C. But C++ compilers will generally optimize semantically equivalent code better than C compilers will, because C++ gives the compiler more freedom.
I.e. your data structure is like this:
generic_hashtable Hash; // (hash->int)
Foo *values; // where my typed data is.And yes, C developers also tend to avoid ordered containers.
Well then, don't use zero-terminated strings for proper string processing. You don't have to use zero-termination, even when some programmers in the 70s and 80s were convinced enough of it that abominations like strtok() landed in the standard.
You can choose between zero-termination and having to convert strings back and forth when using idiomatic libraries.
"A magnitude faster" for doing what? Typical usages of zero-terminated strings are performance-uncritical. And note that zero-terminated doesn't preclude using separate length fields.
Sane programs use store length of strings explicitly where strings get longer and/or performance is a concern, just as it is the case with other types of arrays.
Is it? I've been programming strings for 45 years now. Including on 8 and 10 bit machines. All that space efficiency goes out the window when one wants a subset of a string that isn't a common tail.
The simplicity goes out the window as soon as you want a substring that isn't a common tail. Now you have memory allocation to deal with.
The performance goes out the window because now the entire string contents has to be loaded into the cache to determine its length.
> length-prefixed
Are worse. Which is why I didn't mention them.
> Sane programs use store length
Meaning they become length-delineated programs, except it's done manually, tediously, and error-prone.
Whenever I review C code, the first thing I look at are the strlen/strncpy/str** sequences. It's almost always got a bug in it, an off-by-one error.
No, I recommend everyone to use whatever fits the situation best. It might be a 2 byte start index and a 1 byte length fields that expresses the length as a multiple of 12 bytes. It might be rope data structure. Or it might be whatever. "String" is not a super well defined thing, and I don't understand why everybody is so super concerned about a canonical string data type. String data types are for scripting languages. 99% of my usage of string (literals) is just printf and opening files, and C does these just fine.
Zero terminated strings are only a default thing for string literals that does indeed bring a little bit of simplicity and convenience (no need for a builtin string type and the associated bike shedding, and only need to pass a single pointer to functions like printf).
> Meaning they become length-delineated programs, except it's done manually, tediously, and error-prone.
Not sure when is the last time I found it "manually, tediously, and error-prone". There are very rare cases where I have to construct zero-terminated strings from code, or need to strlen() something because of an API. And even when these cases occur they don't bother me at all. Stuff just works for me generally and I'm moving on. I have probably 500 stupid bugs unrelated to string handling before I once forget a zero terminator, and when that one time happens I just fix it and move on. On the plus side, given that we're in C where there are no slice types, zero-terminated strings spare me to pass extra length values for format strings or filepaths.
Sometimes I envision being able to use slices but I have some concerns if that would be an actual improvement. Importantly it should be about arrays and not just about strings. Strings are arrays, they aren't special.
I think a good design for slices could be one whose length can never be accessed by the programmer, but which can be used for automated bounds checks. Keeping size/capacity/offset and 43 cursors into whatever buffers separate is actually correct in my view from a modularization standpoint, because "String <-> Index/Size/Offset etc." isn't a 1:1 relationship.
> Whenever I review C code, the first thing I look at are the strlen/strncpy/str* sequences. It's almost always got a bug in it, an off-by-one error.
You will have to look quite a bit to find strlen() or strncpy() in my code. I'm not advocating for them, and not advocating to build serious string processing on top of zero-terminated strings.
When you're processing actual buffers full of text or binary data, and performance matters, of course you are not advised to use an in-band signaled sentinel like zero-terminator is. Use an explicit length for those cases.
It was 40 years of throwing UB optimizations at it, that made its current performance come true.
This is nothing special, any language can eventually reach that reality with similar investment.
The length is known statically, but whether the index is in-bounds may not be.
The binary search GP talks about is exactly one such case, go on Godbolt, write up a simple binary search (with a static size so you get code), and you’ll see that the compiler checks against the literal array size on every iteration.
"Emit" means "to send out", eg "emit a strange noise", "emit radiation".
- trying to access an arbitrary element in a slice, the compiler will emit bounds checks (`if index > len: panic()`) to avoid an uncontrolled out-of-bounds memory access — https://godbolt.org/z/cbY5ebzvK (note how if you comment out the assert, the code barely changes, because the compiler is adding an invisible assert of its own)
- if the compiler can infer that `index` will always be less than `len`, then it will omit the bounds check — https://godbolt.org/z/TTashYnjd
(And thanks to the other person as well, who presumably deleted the same comment after seeing yours.)
And what is the runtime cost of all the mitigations put in place because we don't use a memory safe language? Stack canaries, safe stacks, ASLR, control flow integrity, code pointer integrity, runtime attestation, library re-linking and randomization. Not to mention sandboxing techniques and other system level mitigations.
I suppose I should thank the C language for job security as a security engineer.
To be devil’s advocate and take the other side of this argument: I would rather the CPU I paid for spend its cycles performing actual application logic. Every cycle spent executing all this “safety code” overhead feels like me having to pay for a developer’s sloppiness.
I feel end users pay a high cost for all this runtime checking and all these frameworks and levels of abstraction.
And second, would you really rather deal with security holes on an on-going basis?
The problem with C is not that you can write unsafe code in it, it is that the design of the language -- and pointer aliasing in particular -- makes it impossible to perform even basic sanity checks at compile time [EDIT: or even at run-time for that matter]. For example:
int x[10];
...
int y = x[11];
In any sane language, that would be a trivially-checkable compile-time error. But in C it is not because there are all kinds of things that could legally happen where the ellipses are that would make this code not violate any of C's safety constraints. That is the problem with C, and it is a fundamental problem with C's design, that it conflates pointers and arrays. C was designed for a totally different world, and it is broken beyond repair.
It is also worth considering that there is nothing preventing C compilers from inserting compile time checks to address examples such as yours. I just tried to compile a similar example in gcc and it does catch the unsafe code with warnings and the optimizer turned on.
[0] Ok, I guess you could #define x something, but that's not interesting from a static analysis perspective.
There is an asymmetrical risk here. On one hand the compile might emit checks that aren't needed. On the other that the programmer will fail to insert a check that is needed.
The problem is that for CPU programming errors are application logic too and they are run, cf. protected memory. You don't run everything in ring0 do you?
First it's up to the programmer to put them in. Then hopefully the compiler will optimize away the unneeded ones. If the programmer though biffs and forgets then oops. And of course classically we blame the programmer not the system he's been forced to work under.
That seems like a reasonable thing in 1982. Which is 40 years ago.
It would be better if the compiler implemented the checks automagically and removed ones it knows it doesn't need. And bonus, if the programmer puts one in, leave it alone.
If your transistor/cost curve has a doubling time of 3 years, 156 weeks. A 5% difference is approximately 11 weeks.
If your transistor/cost curve has a doubling time of 2 years, a 5% difference is approximately 8 weeks.
How fast do we have to get before safety is table stakes? Focusing on raw unsafe speed wouldn't be a normalized metric in any other industry. I'll spend 8-11 weeks of performance gains on correctness.
C was an inside job.
Yes, they can defeat a few classes of exploits, but generally not the ones that lead to really bad outcomes.
> ~70% of the vulnerabilities Microsoft assigns a CVE each year continue to be memory safety issues
Memory safety is a leading source of serious bugs in a big group of operating systems, browsers, image and file parsers, and more.
It does seem likely we can stop this, and it doesn’t seem likely to me we can fix it in C. We have tried and failed for years at that.
Hardware memory tagging, the language itself is beyond hope.
How is this not a win? We only have so many decisions we can make per day.
Unlike general purpose languages WUFFS has a very specific purpose (Wrangling Untrusted File Formats Safely) so it doesn't need to worry that it can't solve some of your problems, which frees it to completely refuse to do stuff that's unsafe, while going real, real fast.
WUFFS gets to completely omit bounds checks, which you probably wouldn't have dared try in C because it's so obviously unsafe, but WUFFS already proved at compile time that it can't ever have bounds misses so it needn't do these checks. WUFFS gets to also omit integer overflow checks, because again it proved at compile time that your code is correct and cannot overflow for any input. And since WUFFS knows how big the data is at all times it gets to automatically generate loop unrolling and suchlike accelerations.
WUFFS isn't a general purpose language, it doesn't have strings, it doesn't have growable arrays (what C++ calls "vectors"), it doesn't have any dynamic memory allocation, but then we weren't talking about how much you love general purpose features, we were talking about safety preventing security bugs. Which is exactly what WUFFS does.
On the other hand what computer security people worry about a lot is that the web browsers made by reputable organizations and teams of competent programmers nevertheless contain security flaws that can be exploited by a maliciously-written website to cause those browsers to do unexpected and dangerous things.
Many of those security flaws in otherwise well-regarded software are due to memory management errors that just aren't present in safer languages, or they're due to type errors that wouldn't be present in more type-safe languages.
There are some implementation bugs that could be present in any language no matter how many safety features it has, but many security bugs aren't due to, say, an incorrectly specified algorithm, they're due to the programmer asking the computer to do something that's literally nonsense, like asking for the fourth element of a list that only has three elements, or recording that someone's age is apricot. Programming languages with powerful nonsense filters can remove a lot of those kinds of security bugs. (And powerful type systems often give programmers mechanisms to tell the compiler more about the program so that it can filter out more kinds of nonsense than it would otherwise.)
Runtime mitigations exist to mitigate some of the latent risk associated with programming in unsafe programming languages. We use them because they're our best known approach to continuing to use those languages without letting script kiddies own us like it's 1993.
Obviously there's a lot of little details you'd have to work out. Like how to make such a trusted compiler in the first place, and how to sandbox unsafe code and legacy applications that were compiled by an untrusted compiler.
If this seems like it's far-fetched or too much work, consider what lengths high-performance hardware devices like HPC network interfaces go to avoid system calls at all costs, to the point where applications talk to the hardware directly. Is that really a sustainable practice long term? And how can anyone audit the security of such hardware devices?
I'm pretty sure the discovery of Meltdown/Spectre and similar speculative execution attacks would completely wreck this model. The fix for those exploits has been to make the isolation barriers even stronger but if you don't have them at all you're wide open. If you had such an OS but then had to split it back into separate address spaces you've now lost the performance gains and just have a slower OS that is harder to develop drivers for.
That's pretty much how the Amiga worked, and that level of technology achievement is still unsurpassed today.
I'm sorry, but the C language toolchain is not great. The only part of the C language toolchain that is good is it's platform support. Every platform has a C compiler.
However, that's hardly something most devs will care about. At this point, we are pretty much all targeting Arm or x86 (Sorry PIC and MIPS devs). And every new language that's cropped up at a minimum supports both those platforms and the major OSes for those platforms.
So once you dismiss that point, what are you left with? Tools for catching memory leaks? Well, sorry, but pretty much every language has those tools and better. Particularly GCed languages. Profiling tools? Debug tools? To say the C debug tools are "good" is to say you've not used any other toolchain.
But let's also point out C's horror story that is "dependency management and building". How do you grab dependencies for and build javascript? npm install, npm build. How about C? Well... are you using auto tools? Cmake? Ninja? Scion? makefiles? etc... Oh, and how do you get dependencies? Well, hope you like reading readme files and decipher cryptic "symbol not found xyz" compiler errors.
Suffice it to say C's toolchain isn't anything to be proud of. It's a relic of the 70s that to this day gets in the way because the community has practically given up on the idea of fixing the glaring issues (Too hard, everything already depends on X working the way X does).
So why risk switching languages? Well, because the C toolchain is a dumpster fire and you likely aren't trying to write new code for IBM RPG servers.
Hey, I know that! Challenge accep... Eeeeh, nevermind.
Please take a few days to review John Regehr’s excellent blog. I’m certain that you’ll find dispelling your ignorance rewarding.
> How do you grab dependencies for and build javascript? npm install, npm build.
Npm is the poster child for supply chain attacks.
Haven't heard of him but will certainly give a read. Thanks for the heads up.
> Npm is the poster child for supply chain attacks.
The only reason C doesn't (often) have similar supply chain attacks is because pulling in dependencies is so hard that you aren't likely to end up with a 10k dependency project.
Hard to say that's really a plus.
Other ecosystems have their own problems and certainly aren't perfect. However, the toolchains are generally leaps and bounds ahead of what C currently has.
> Hard to say that's really a plus.
I'll definitely call that a plus. All dependencies introduce risk. Sure, we can't avoid all dependencies, but carefully evaluating them and keeping the list small is a big win for maintainability and security.
If a random library has a 0.1% chance of having malicious code (I'd say it's more like 1%, but let's be generous), a 10K-dependency program is guaranteed to be pulling in at least something malicious.
Or the lack of canonical package manager means the developer has to actually spend time to inspect the quality of each dependency instead of `npm install is-odd`ing like there is no tomorrow.
It also acts as a "filter" to prevent adding complexity to the dependency tree.
Just a heads up, this reads to me as condescending.
I regularly read his blog and I found nothing to back you up. He mostly work on a generic compiler toolchain (LLVM) other than C-specific tools and his work easily translates to many other languages using LLVM. And even a rare C-specific tool like C-Reduce is not that hard to adapt for other languages; C-Reduce is actually language-agnostic and works well for many other curly-brace languages as well.
You can't trust HN comments. Most of them sound good to laymen. But to a trained eye, it's a comment tree of cards.
Like it or not, a problem C has is that it's pretty much impossible to determine why a piece of memory is currently being retained. Tools like valgrind and jemalloc can give you good starting points but really can't point to issues where "This memory is being retained because of this linked list over here"
Languages with GCs can, at any point, dump the heap and provide tools for analyzing that heap to point to the exact reason why a bit of memory is currently being retained.
That's not really "blind" praise of GCed languages, simply a fact of how they operate.
I think there’s a wide open research space to work on taming the aliasing problem. There are PhDs to be had, and maybe even Turing awards.
[1] C gets to cheat a little bit here because Unix and friends are its runtime.
Zig's `zig cc` is a killer feature that doesn't even require using Zig-the-language at all. `zig cc` is an LLVM-based C compiler that gives you trivial cross-compilation for existing C codebases, adds effective caching, and can be easily installed on most operating systems with `tar -xzf`.
https://andrewkelley.me/post/zig-cc-powerful-drop-in-replace...
pip install ziglang
python -m ziglangI stopped reading here. This is like saying typing speed doesn't matter because most of your time is spent thinking about what to type. If your argument basically evaluates to saying that it doesn't matter how productive you are while doing the actual activity of your job itself... at some point it's like, I dunno, do you actually believe what you're saying?
Also, not to go off on too unrelated/unhinged a tangent, but, I've also started noticing in the last five years this widespread fear of actually sitting down and programming. There's a weird meta-culture where people want to do anything but sit down and write code, because it's hard and scary. I hear arguments made all the time for how important everything is — aligning stakeholders, scoping tasks, communication, mentoring juniors, soft skills, whatever. Everything except writing the actual code, which is somehow a mere technical triviality that anyone can do, or something.
We have all of these tools that help us to write code while making few mistakes. From constrained DSLs/configuration languages, schemas, static typing to automated tests etc. But in the end we still have to exert all that energy to sit down, focus and do the coding.
Meetings, design, coordination etc. All of those things are important, and they can be exhausting in their own way. But programming is by far the most challenging, hour per hour (pound for pound). Making all of these micro decisions, think about the impact of your code, the readability, the correctness. It is _at least_ two times harder and more draining than anything else I do, even when I'm in a flow state and the actual work is fun, more fun than all of the other tasks.
And I agree: It is the most important thing. I mean all of the other stuff is basically there to support the core task that solves your problems: producing working software (except if it isn't but then you have political problems). There are seemingly infinite dimensions for improvement as well, tradeoffs such as performance, efficiency, robustness, usability, leverage, creativity...
I’ve programmed in C for 20+ years and then recently switched to rust. When fellow C programmers ask me how Rust is going, I say “it’s modern” and “it’s consistent”. What I mean by that unlike C, Rust doesn’t have 50 years of baggage: string functions that handle null termination all differently, complicated implicit promotion rules, null-terminated strings that haven’t been a good idea for decades, apis that we’re completely busted by posix threads but still exist, functions that cannot be used safely that still exist, code snippet examples with horribly broken unsafe code that beginners copy-paste, dubious and bad type-based aliasing, rampant use of UB, etc etc etc
Sadly Rust is not a better C. Maybe it’ll grow into one some day. Today it’s a better C++. Sadly I’d still chose C to do C’s job.
All I want from “better C” is to fix the obviously bad problems from C’s history. Or rather, maybe that’s the committee’s job.
At any rate: it’s embarrassing that we’re still so dependent on a language that’s so broken. And it’s sad that there doesn’t seem to be a path forward.
1. C language toolchain -- No specifics (save static analyzers, ... because C needs static analyzers to catch C specific bugs...). What do you actually feel you're missing? Most of these "new C" languages use the same backend as a C compiler. What can't you use?
2., 3., and 4. -- Just chicken and egg FUD.
Not to mention it's "'Better X' doesn't matter" fundamentally doesn't understand the competition.
A sample quote: "That's not just one but eight(!) killer features (re: Java). How many of those unique selling points do the C alternatives have? Less than Java did at least!" WTF?! "But Java had 8!" is laughable.
Another: "[A]ny safety checks put into the competing language will have a runtime cost, which often is unacceptable". We can also produce a car without windows, doors or a chassis that will survive a crash, and it may be faster too. We may even get lucky and live to tell the tale.
Again: "Aside from Jai, is anyone C alternative really looking to pursue having killer features?" Aside from Jai?! Jai. Really? Oh, I remember you just discounted safety and correctness as killer features. ... But Jai. And nothing against Jai. But a language which hasn't been, which may never be, released to the general public. Just, eyes wide open, wow.
I hope this is the peak of anti-Rust/pro-C silliness, but I know it's not.
It's a position paper meant to sketch out the negative space that a viable C replacement can't meaningfully compete in, so that its designers can focus attention on things that actually add value. If you don't acknowledge your enemy's strengths, you'll never really be able to fight them.
Yeah, I get it and I'm not sure it does that job well enough to be taken seriously.
It explicitly says in the post... for example static analyzers. What is the "Frama-C" equivalent of any language that bills itself as a replacement / competitor of C?
> Most of these "new C" languages use the same backend as a C compiler.
Most of them use LLVM as far as I know, which does not target plenty of obscure platforms.
> Most of them use LLVM as far as I know, which does not target plenty of obscure platforms.
Yeah, writing software in the old language is more convenient on obscure, rare, niche platforms, because it's older than dirt. More chicken and egg nonsense. It's not impossible to have LLVM add additional targets, right?
It seems like you're being needlessly hostile. There is nothing personal about this discussion, and it's not nonsense. I write embedded software that needs to run on PIC18 microcontrollers. Support for that in LLVM was dropped about 8 years ago. Do you think it's reasonable to say to someone "just add a new LLVM target, it's not literally impossible"?
I'm profoundly disappointed. This article's reasoning is not good.
> Do you think it's reasonable to say to someone "just add a new LLVM target, it's not literally impossible"?
No, but that C is already pervasive because it's been around 50 years isn't a case "against an alternative to C" as much as it is a headwind. That's fair. C has a massive head start and no one should discount that. But I'm not exactly certain that was the argument he was making.
If you can't use Rust because of your specific circumstances that's ok - use C! But don't use "my current circumstances prevent me from using Rust" as an argument as an argument against Rust in general - which is what you're doing.
First of all I'm just summarizing the article, I'm in no way arguing against people using Rust. The article itself argues against C alternatives, and explicitly describes Rust as a C++ alternative. So the article is not even arguing against Rust! If we can't agree on such a basic reading of the article, it just seems like we're going to pointlessly talk past each other in the comments.
I don't know why you say 2-4 is FUD.
> Memory allocation, array and string handling are often tricky, but with the right libraries and a sound memory strategy, it can be minimized.
Such a handwavy deflection. Parsing strings in C is a joke and also a minefield bursting with vulnerabilities. And if you use null terminated strings (which you are, let’s face it) it’s slow.
(strncpy doesn't 0-terminate when it hit max length, nor stop when it sees its first 0 in the source buffer; it acts almost entirely like memcpy except that it writes zeroes to the full length of the destination upon seeing a zero in the source. This is allegedly useful for populating fixed-sized buffers in historical Unix, and can plausibly be useful for writing out tar headers, but in practice very little code in the wild does anything observably different if you #define strncpy memcpy.)
I don't we should stop trying to improve just because an existing solution exists, especially in the space of systems programming which has barely moved in decades
The truth is, parsing strings in C is as easy as in any other language. You have a string array and a length field, and you scrub through it from left to right with a cursor. Done.
What you do not get in C is creating lots of string objects willy-nilly, and concatenating them with a plus sign, like you do in scripting languages.
You can strap basically any language on top of the C ABI. Many contender languages either use it natively or offer low-cost/no-cost C ABI bindings, specifically as its the lingua franca ABI.
You can check (5) off your list if your choice of replacement language has easy C ABI interop.
[edit]
(1) isn't particularly relevant either. These days the tooling for detecting memory leaks, data races, bugs, etc, operates at the debug info (DAWRF/dSYM) level. It doesn't matter what compiler or input language generated the debugging information, the tools should work just fine. You can run valgrind on your Rust binary for instance. [1] Is there any such tooling that specifically depends on the input source being C? I guess some static analysis tools?
[1] https://nnethercote.github.io/2022/01/05/rust-and-valgrind.h...
A platform will define an ABI which, as I understand it, explains how parameters are passed to and returned from a "function", what types are supported and how they're encoded, at the processor register and stack level.
C, being the "portable assembler" [1] that it is, maps its types and functions pretty easily to that platform ABI, but I've been seeing a fair amount of confusion about that ABI being about C rather than any language that can link through that platform ABI. Couldn't it just as easily be called, I dunno, the Ada ABI?
[1] not really, except for the sake of this argument
C with:
- bounds checking
- use after free checks
- modules
- sane metaprograming/template
- tagged union built in
without:
- macro
- pre declaration
- split header/source
That's it, no borrow checker, no weird syntax, no nothing
So far D is the answser for me, but i'm worried about its future, will they keep improve the language in that direction? or will they continue with their high level stuff
Zig/Jai/Odin so far are looking interesting, the problem is they all depart from the C syntax, wich is annoying, the only syntax i'd like to change is the primitive type, i32/f32/size/usize, and that's it, no need more
I have pondered forking tinycc and using that as a startpoint for a more conservative set of changes like you describe. It would limit the amount of work to be done and lead to something useable quite quickly.
Also no language server is a hard pass, tooling is important
Whenever a CVE is discussed, even if the code was from Google, Microsoft, or Apple, commenters immediately point out that these companies must have hired some "bad programmers", because a "good programmer" wouldn't have made such mistake. Given that even top software companies continue producing CVEs on a regular basis, there is a shortage of people capable of writing C.
A lot of programming skills are transferable (algorithms, architecture, system APIs, debugging), so you don't really need to hire "language X programmer".
> It turns out that people don't always agree on what the pain points with C is.
People have different problems so they should probably use different languages. Don't even try to make a language that will solve everyone's problems.
> Also, while the company has experience recruiting for C developers, it doesn't know how to recruit for this new language.
If the language is so hard that people have to have special knowledge to use it and can't learn it quickly then it's probably not a good language.
> For a business it's whether despite the downsides the language can help the bottom line: "Is this valuable enough to outweigh the downsides?"
Exactly, it's solving real problems that matters.
> The "build it and they will come" idea is tempting to believe in
A better idea is "build it and we will come because we need it. Then maybe other people will come if they need it too."
This is completely wrong. The best counterexample is probably ATS http://www.ats-lang.org which is compatible with C, yet also features dependent types (allowing us to prove arbitrary statements about our programs, and check them at compile time) and linear type (allowing us to precisely track resource usage; similar to Rust)
A good example is http://ats-lang.sourceforge.net/DOCUMENT/ATS2CAIRO/HTML/c36.... which uses the Cairo graphics library, and ends with the following:
> It may seem that using cairo functions in ATS is nearly identical to using them in C (modulo syntatical difference). However, what happens at the level of typechecking in ATS is far more sophisticated than in C. In particular, linear types are assigned to cairo objects (such as contexts, surfaces, patterns, font faces, etc.) in ATS to allow them to be tracked statically, that is, at compile-time, preventing potential mismanagement of such objects. For instance, if the following line:
val () = cairo_surface_destroy (sf) // a type error if omitted
> is removed from the program in tutprog_hello.dats, then a type-error message is issued at compile-time to indicate that the resource sf is not properly freed. A message as such can be of great value in practice for correcting potential memory leaks that may otherwise readily go unnoticed. ATS is a programming language that distinguishes itself in its practical and effective support for precise resource management.In the end, though, you can accomplish the same (from an outcome perspective, not necessarily from a satisfactory one) in all of them, so I try not to be overly biased, although convenience vs correctness seem to be the main issues at play here.
(Edit: why the downvotes? Is Zig out of favor? Is it because this may read as a slight on Rust?)
I’d like to use Zig a bit more, but the standard library is still going through some significant changes (it seems) which makes it slightly harder to commit to at the moment.
In general I wonder if some of these languages would actually benefit from Jai’s approach of deferring availability until the language is ready. While getting early adopters is great for getting feedback early, it can also disuade potential users that try the language before its ready and decide to leave it alone as a result.
This is hard to describe without getting deep into Rust details, but I think the ability to encapsulate unsafe code in safe APIs is just as important as what pure safe code can do. (Of course those two things end up being intimately related.)
Seems like this might be a good place to ask: which of the C replacement languages mentioned (or not mentioned) support inline assembly? Do any of them do it better than GCC/Clang? For me, I feel like the ability to drop to assembly in a pinch is a necessary feature. I'm sure I could technically get by linking an external file, but I'd be more interested in a language with inline ASM support than one that doesn't allow it. Thanks!
Rust: https://doc.rust-lang.org/nightly/reference/inline-assembly....
D: https://dlang.org/spec/iasm.html
Ada: https://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ug...
Cobol: https://www.ibm.com/docs/en/cobol-zos/6.2?topic=appendixes-a...
Pascal: https://wiki.freepascal.org/Asm
Python: https://pypi.org/project/il/
You can almost name a language and find a route to inline assembly :D
Zig: https://ziglang.org/documentation/master/#Assembly
Odin: Looks like it's a dead language, but it was on the docket. https://github.com/odin-lang/Odin/blob/master/misc/roadmap.m...
Jai: https://github.com/Jai-Community/Jai-Community-Library/wiki/...
eC: No clue. Not a great name for googling.
C is great mostly because it's easy to learn, and it has many other advantages.
I dislike all those new languages because they have too many features, and they're non trivial to learn.
I've looked at some rust codebases and I don't think there will be a lot of people who will want to maintain them. Rust is difficult, even though it's an awesome language.
A language that could compete with C would:
* Be fast to compile
* Have some quality of life things you can find in the C++ STL: map, string, etc
* Look a lot like C, meaning it would not try to innovate too much.
To summarize, it would be a lighter version of C++.
What exactly do you mean? Learn the basics? Become productive? Memorize the entire specification? I'd argue C isn't significantly faster at any of these than most other languages.
The "difficulty bar" must be low. This is why english is a popular language.
> I'd argue C isn't significantly faster at any of these than most other languages.
Yes it is. It has a lot of flaws, but it's popular for all those other reasons.
A "better C" language doesn't have to "win the popularity race" to be useful, it just needs to (a) be easy to learn coming from C, (b) integrate well into the existing C/C++/ObjC ecosystem, and (c) easy to install and across all supported platforms.
A "better C" cannot and should not replace C for all use cases, but it should at least augment C by fixing its design wart.
The actually depressing thing is the "winner takes all" mentality though that seems to be prelevant in the programming world.
"It is difficult to get a man to understand something, when his salary depends on his not understanding it."
There's an oversupply of enthusiastic engineers trying to provide C alternatives and it's creating massive bike-shedding and yak-shaving problems.
Language designers are encouraged to give a presentation if it's about building quality software using a language. Otherwise, no.
The biggest culprit I've met on this front is the POSIX stat family of functions.
This is true for both software and hardware. There have been plenty of leaps and bounds such as SSD speeds compared to HDD speeds; but even there, SSDs will not supplant HDDs until there is price parity between the two technologies. Until then, data will be stored on SSD when fast access is critical and on HDD when there is too much of it to justify the cost.
Getting a company to switch operating systems, database vendors, or cloud providers is likewise an uphill battle. They don't call them 'walled gardens' for nothing.
I am building a new data management system that I think is much better than file systems at storing and managing unstructured data and better than RDBMS at managing structured data; but I wouldn't have even dreamed of starting the project if I wasn't confident that it was at least 2x better at a minimum.
I realize that this is radical, but if people could work with me on this, this could solve all your problems, no really, it could solve all your problems. And if it can't solve all your problems yet, then modify it to
https://cboard.cprogramming.com/c-programming/181160-hi-i-ha...
There is no need for another language. Just use this
Gregory
In terms of relying on tested and supported infrastructure lots of these projects use llvm.
Why do people keep lumping Rust into the category of a "C++ alternative". Rust is a C alternative and can be used places that C can but C++ cannot. i.e the linux kernal and baremetal silicon (C++ can be used in the embedded but I've never heard of it being used without modification for that environment).
> My language (C3) is fairly recent, there are others: Zig, Odin, Jai and older languages like eC.
Further he seems to claim that Jai is a C alternative when it's explicitly described by it's creator as a C++ alternative.
To fragment the industry with more languages is a hidden cost that wont end well for anyone.
KWh is approaching $1, prepare by choosing the proper languages.
This one is the main point. Highest possible speed while consuming as little resource as possible. Put in checks, and most of those checks will run redundantly compromising performance.
-- Fran Allen, from Coders at Work
How about all guarantees static analysis brings to data races, memory ownership, etc? No runtime cost. C tools do not bring this.
The rust implementation was much less code than the C version. It generated a bigger assembly but it ran 20% faster or so. (I don't know why it ran faster than the C version - this was before the noalias analysis was turned on in the compiler).
Its now about 3x faster than C, thanks to some use of clever layered data structures. I could implement those optimizations in C, but I find rust easier to work with.
C has advantages, but performance is a bad reason to choose C over rust. In my experience, the runtime bounds checks it adds are remarkably cheap from a performance perspective. And its more than offset by the extra optimizations the rust compiler can do thanks to the extra knowledge the compiler has about your program. If my experience is anything to go by, naively porting C programs to rust would result in faster code a lot of the time.
And I find it easier to optimize rust code compared to C code, thanks to generics and the (excellent) crates ecosystem. If I was optimizing for runtime speed, I'd pick rust over C every time.
We've known for decades that compiler mitigations, fuzz testing, and dynamic instrumentation are excellent and necessary components of writing more secure C. But they don't secure C programs, because C itself is fundamentally unsafe.
> No, they're not static tools but being dynamic has a host of advantages too.
Advantages that any compiled binary can enjoy.
Here, you can of course make a more safe language without runtime penalty just by supporting richer build-time/static checks, but the modest and meaningful gains you make within that constraint will never be enough to displace C.
You need to be providing something meaningfully more in areas where C hasn't already squeezed out most gains. The OP tastefully avoided making this article directly about C3, but if you go and look at the summary of features, you can see how this philosophy connects with what they're working to build.
If you could pay in performance a little bit, but have a clean, portable, beautiful debugger experience, nice libraries like 'Java' with a 'stable ABI' ... nobody would use C. We'd all use 'that new thing'. And we would just buy slightly faster hardware for the IoT whatever.
We use C++ smart pointers all over the place, and they come with a 'cost'. We mostly 'happily pay the price' because it's worth it most scenarios.
Engineers obsession with performance is a bit of a curse. Rarely do we really need that much. Kernel code, sure, IoT and remotely controlled toy cars, mostly not.
The sad part is that the mainstream alternatives that I have on the radar are _even slower_ to build.
And usage of the preprocessor in APIs is allowing for good things, at least for a compiled systems language like C: Macros allow to get rid of boilerplate on the syntax level and if-defs allow for compatibility. Both points' significance is reduced a long way in other languages, because they have stronger facilities for abstraction, BUT this abstraction comes at a non-significant price and cannot replace all significant uses of the preprocessor.
So I'm still stuck with C. The solution is designing header files carefully, resulting in build times that are lightning fast, at least in relation to most other mainstream languages. And there is always the option to go for "unity" builds.
As to improving modularization, in order to fix compilation speed issues I was trying out the "program as a single compilation unit" approach advocated by handmade people, and I found that I had to spent a good amount of time in moving code between modules, and their subsequent headers. So annoying... Perhaps it wouldn't be so infuriating if I didn't spend the past 15 years coding in languages where this is a problem solved automatically and invisibly by the compiler.
Suppose you have bounds checks on array accesses, but your program is 100% correct and the panic case is never hit. Doesn't the branch predictor essentially make the bounds check free? Or very low cost at least?
That said, a lot of pedestrian code that isn't running particularly hot would probably be better off including runtime checks by default.
EDIT: What language uses base-60??
You won't be able to use the standard library, but it's doable.
Let's go in a fantasy world where we have a B+ language which should have been C: remove typedef/_generic/typeof/restrict/enum/etc, well all those horrible things. Only one loop statement, loop {}, no switch ofc. Only sized/signed core types with properly sized/signed literals: ub/sb or u8/s8 with integer literals like 123ub/123sb, sw/sd..., udw/sdw... , uqw/sqw..., fdw/1.23fdw (f32), fqw/1.23fqw (f64) (for some hardware ymm/xmm/zmm with fancy literals, sorry I am not aware of the fine details of the C/CPP lexer). Ofc, no integer promotion and no implicit cast (void* would still not require any cast). Compile-time and runtime cast (not like with that horrible c++ syntax), same thing for consts (even though nowadays it is the the compiler which detects real compile-time constants), yes you may have to "static const" your literals. Oh, and for the C preprocessor, do fix that variadic-args function-like macro for good (I wonder if gcc the way is not better than c++ ISO way).
Without all that, it should be "reasonable" for a small team of average-skilled or an individual to write a naive compiler until they don't forget they can hit a goto at any line. And they are those who say "but feature A is cheap to implement"... 1 million cheap features later... well, you get the picture.
The really hard part for ISO was to keep C actually "cheap" to implement, this is where they are failing hard.
As for the gcc extension based C dialect for kernel developement, no salvation here, C is really too alien (or even B+). Namely the little ISA abstraction is not "enough", so either the kernel goes full "slow" with plain C and assembly (_not_ inline assembly ofc), or full assembly on all of its fast-paths which would require proper binary defined specs (I am currently researching how bad this could be).
I do believe the open source communities have the strength to maintain parallel assembly _significant_ code bases (which do the same thing), big monolithic corpos... not so sure...
> [...] Why? Because the actual programming is not the main time sink. In a business, what takes time is to actually figure out what the task really is. So something like a 10% or 20% "productivity boost" won't even register.
This reminds me of Amdahl's Law [1], about how a performance optimization (parallelism) has small returns because the performance improvement only affects a small portion of the overall process.
This annoys me so much in Java debates. The argument goes something like "It doesn't matter that Java is verbose and requires hundreds of files because the IDE generates them for you". And this is true... when you write the code. But the real friction is when you must read or change the code in response to new requirements, which is exactly what we as engineers spend the bulk of our time doing.
With this knowledge I choose to align myself with languages and platforms that respect C and complement it (Lua, Objective-C, etc) instead of languages and platforms that try to supplant it (c++, java, swift, rust, etc)
Also, you got any recommendations for a c library for server apps?
1. In other words, the size of a "hello world" program matters, as does the storage space and time I need for the compiler toolchain and libraries.
What language should I use. I am not interested in writing large, complex programs.
You can use C, but with better syntax.
https://cboard.cprogramming.com/c-programming/181160-hi-i-ha...
I'm looking for contributors. :)
To give a concrete example, I was able to write production-ready code with 2 weeks of Rust experience and nobody to provide support for questions (beyond reading StackOverflow), and that code was more reliable and less buggy then the Python/JavaScript code our company was otherwise writing, even though we were collectively much more experienced in those stacks. I doubt we'd have gotten that with C, even though we had a bunch of developers who were experienced with C.
In larger programs on full-fat operating systems, programs tend to be much larger (esp. if you include libraries - dealing with libraries being one of the most complex things in C), and you have to deal with sophisticated allocation patterns, and complex types (webs of pointers), abstractions and business logic which is where C basically leaves you on your own and provides very little in terms of structure or guard rails.
Some problems require solutions which are unable to be solved in C in a safe way.
I posted this less than 24 hours ago.
This might be what you want, and there's no overhead at all.
It might actually speed up your code. 15x faster regex
I just made this. I'm looking for contributors :)
The vast, vast majority of programmers aren’t doing their computing by writing C anymore. It’s been the better part of 20 years since you could plausibly call it the Lingua Franca of computing.