Ask HN: Why do/don't you use C?

13 points by purple-leafy 2 days ago

I've been having a blast writing projects in C lately (emulator, raycaster) and using it extensively for a game engine I'm making.

But I always hear people saying "just use Rust or Zig" - but C is actually a great time, and I'm happy the naysayers didn't put me off.

So, do you, or do you not use C?

If so: why, and what for?

If not: why not, and what do you use instead?

wruza 2 hours ago

I’ve learned C in 00s and don’t think it’s hard with all the usual talks talked about it.

But eventually I grew really tired of handling everything by myself or failing at attempts to enhance C with syntactic augmentations in a sane way. You just plow through finalizations again and again after every change, and invent all sorts of rube goldberg machines to correctly quit a failed function.

C was designed with one-shot programs in mind, which was the default. You just read and write until EOF/error and then exit, simple (akin to Perl which had neither memory management nor GC - your script was just expected to finish soon and not linger for hours). Otoh, modern highly stateful programming is a great PITA in conjunction with C.

Maybe Zig and Rust would do better for me, idk, but somehow I pivoted into non-performance programming. I’m not using anything instead. There’s no “business” in C and no money, unless you’re lucky with a rare low-level job.

jvreeland a day ago

> If so: why, and what for?

A huge amount of important existing software is written in C. If the code is already written or needs to inter-operate with other pieces in many languages I typically write C. There are a also lot of things that can be easier in C because that modern safer languages would stop you from doing foot shooting things that C won't it's quicker sometimes to write or prototype in C.

> If not: why not, and what do you use instead?

A not insignificant part of my career has been fixing bugs in C code that's been around forever. It's almost always the same class of dumb bugs that wouldn't exist in other languages. This is not anecdata the research agrees. No new projects should be started in C there are better tools.

rstuart4133 13 hours ago

40 years ago, C was a small simple language that was faster than most (an important attribute on the hardware available at the time), and the language and it's standard library was documented by a very small book that was a masterpiece in technical writing. Some behaviour wasn't defined by the language, mostly in the name of efficiency. An example is "x % y" got whether the underlying hardware gave you. The downside of optimising for speed in that way was porting to a different arch was error prone. It isn't memory safe of course, but back them the alternative was assembler which was far worse in all respects, or a garbage collected language with it's overheads that wasn't available on many platforms.

Now, hardware is so fast speed doesn't matter so much. Many languages such as Python come with nice doco and a standard library that ellipses what C provides. Languages like Rust are in the same league speed wise, are memory safe, run on almost as many platforms and have solved the porting issue. Meanwhile the C standards committee redefining "undefined behaviour" from "implementation defined" to "so whatever we damned well please when we detect it" means you almost have to be an language lawyer to know all it's hidden traps. In other words, the language definition has gone from "a pleasant days reading" to "200k words of formal definition only a compiler writer could love".

Like COBOL, C will be around forever, but the days when it was the best choice for most things passed long ago.

mikewarot 2 days ago

I've been avoiding C since 1981. Macros tend to create unreadable chaos. Cases sensitivity is a bug, not a feature. Null terminated strings are the tool of the devil.

Oh, and type declarations look like line noise.

Turbo / Free Pascal is the way to go. Strings are magically created, reference counted and can just be used, to hold gigabytes, even if they contain nulls. Pointers are easy to see and use. Units avoid build hell.

  • SunlitCat a day ago

    > Cases sensitivity is a bug, not a feature.

    Don't get me started about the importance of whitespace characters in python. Whoever got that idea...!

bruce511 a day ago

You're likely too young to remember the religious language wars, but I expect you'll see some of that here.

The prosaic answer is that there are hundreds of languages to choose from. In most cases we choose the one we know because that gets the task done quicker.

In other cases the language is chosen for you; because existing code, or platform limitations or embedded or whatever.

There are lots of things to like about C - but it's slow to write because it's so low-level. Even dealing with strings is painful.

On the other hand it is low-level, so it's fast, and small. In some cases that is important.

Which is all to say that "context matters" and the context matters much (much) more than persons choice. We have lots of languages precisely because we have so many contexts.

  • purple-leafy a day ago

    Agree that context matters, oh man I seem to choose projects where I don't have to manipulate strings :)

    But for graphics its been quite enjoyable

petabyt a day ago

Mainly ABI: Data structures (structs), strings, types in C are fairly well defined (by each ABI), this enables other languages to talk to code written in C.

Rust data structures are not defined at all (Unless I'm missing some). For example you can't create a rust string in python and pass it to rust code. In order to pass Rust data to and from another language, you have to create bindings and convert between C strings, arrays, etc + deal with overhead

Zig is the only language I've seen that comes close to matching C in this area. Once it gets a stable release I think many more companies will adopt it.

ValtteriL 9 hours ago

As an infosec guy, I used to spend alot time writing and optimizing own projects in C. This was because C was regarded as the language of elite hackers. As a novice I took it seriously.

At some point Python became really popular in infosec and practically all tools were written in it. It was also used by malware authors. This got me to learn the language and realize that it can do everything for me I need C for, while being a lot faster to develop.

So I mostly left C for Python and later for other higher level labguages.

Nowadays at $WORK I occasionally read and fuzz C code. But don't write it at all.

On my own projects I use C#/Elixir/Python/Perl as they let me make the most of my limited free time.

kody 19 hours ago

We use C for embedded products at work. My gut feeling is that Rust or Zig would require way too much time investment to learn and use properly, then you'd still have to deal with interop problems.

austin-cheney 2 days ago

I just never learned to write C. I am just a JavaScript guy and the native APIs for Node.js, aside from permissions and OS service libraries, allow you to go down to just above the OS kernel, which has been more than sufficient for my needs.

I guess I just haven't felt the need to go down to the metal for the original applications that I write. If I were more into extending hardware or writing new 3D engines for AAA games I suspect I would view this completely differently.

Performance is not enough of a consideration. If you are comfortable writing native JavaScript without a bunch of unnecessary abstractions you can expect your code to be about 4.5x slower than C, or about 12x-20x slower at arithmetic. By far the greatest performance limitation of JavaScript compared to things like C, C++, Rust, or Zig isn't the execution speed, but that it is garbage collected instead of manually memory managed via pointers.

ActorNightly a day ago

I use C because Im disciplined enough to not leak memory, through organized code structure. Half of the stuff I write basically mallocs once into a mempool for all my data.

Rust is just too cumbersome to code in, for not real benefit to me.

  • purple-leafy 18 hours ago

    thats an interesting memory paradigm, is there a name for this ? Something for me to try

    • wruza 2 hours ago

      Keywords are: arena allocation, bump allocation, slice allocation.

SunlitCat a day ago

Personally I started out with C in the late 90's early 2000's when C++ under windows was either using a really basic (albeit cool) gcc (which was fine for c but the c++ part wasn't really there for what I wanted it; There were other free compiler but same story kinda) or shelling out serious money to get Visual Studio or something else.

After Visual Studio (first with their Visual Studio Express Editions) became "free" (yeah yeah, not really there are some strings attached tho), I never looked back and used C++ exclusively (more or less; I dabbled in some other "cool" languages here and there, tho).

rednafi a day ago

I don’t need to. I used to dabble in C every now and then, and Go replaced my C usage. I don’t have a use for Zig or Rust, but I like the problems these languages are trying to solve. I work with Python, Go, and, reluctantly, JS. Go’s GC saves me from a few obvious footguns I don’t enjoy thinking about, and it’s fast enough for the things I want it to do.

markus_zhang a day ago

I use C in my side projects it TBH I'm very new to it even after using it for a while.

The reason to pick C is simply because those are all small system programming projects so C is naturally the top pick. I also like C for its simplicity.

YaBa 17 hours ago

I like to keep my mental sanity.

J.K., I really admire who uses C, but it's not for me.

billconan 2 days ago

I had some bad experience debugging a legacy code base in C. People who wrote that code didn't organize their code as they would do in C++. They implement classes using structs and put the member functions everywhere in the code base, (not in pairs of h/c files). The struct fields can be accessed from everywhere too, no enclosure .

aristofun 2 days ago

Everything that I need (web servers, dbs, desktop apps) has already been written in C or whatever.

Why should I waste time reinventing the wheel and prefer C to nodejs or something like java in the worst case?

Genuine question.

  • purple-leafy a day ago

    For me specifically I started learning C because NodeJs / TypeScript / Python are too high level, and you lose the lower level details while you're learning.

    I am "bored" of high level code.

    I've been coding full-time for around 2 years, so still feel like I have much to learn, and bored of using other peoples solutions and taking everything for granted.

    Coding in C (except strings) feels pretty good, because I can't just reach for a library and download someone elses solution - I build alot from scratch and its rewarding when the thing works.

    I feel like I'm learning loads by doing C, as opposed to NodeJs.

    Currently making a low-level graphics project that raycasts to form a 2.5D world. Pretty fun, all from scratch

    • aristofun a day ago

      Learning is a perfectly valid use case and I admire people who are passionate enough to dig deep.

      > I am "bored" of high level code

      But personally I am very "boring" an truly lazy developer, my only goal is to make shit done asap with good quality. And hi level tools (when you know their limitations and nuances) help you avoid a lot of bugs while delivering great quality for majority of real world application.

      I've seen even ruby services handling crazy amount of traffic successfully. Even if it's more expensive cpu/ram wise - there are other tradeoffs that can make it worthwile.

      • purple-leafy 18 hours ago

        Thats completely fair enough to be a "lazy developer" - I am too especially at work :) but in my spare time, for me, I just want to know how the sausage is made!

        Like, how do JavaScript arrays work under the hood? Etc etc

t-3 a day ago

I use C because I use BSDs and Linux and it's often the shortest route when needing to do something that can't be done trivially with a shell script. The documentation is all in man pages, the headers are available in /include, and it's easy to bypass annoying things like type and memory safety when I don't need them, which is most of the time for the small throwaway programs I usually write.

PaulHoule 2 days ago

Memory safety. I don't need the return after a memcpy to be Turing complete. Your C is not your choice because it affects me if your machine gets hacked and is used as a platform to attack me.

Garbage collection. Code reuse is difficult in C because of differing systems for memory allocations. Basically an application doesn't really know if a library is done with a buffer and the library doesn't know... but a garbage collector does!

The ideal reusable library in C would be able to use a buffer from the application if the application had one ready. In cases where it had to build up a complex linked data structure you'd like it to pass it custom malloc and free so it can use your arena allocator, etc. In Java (Lisp, Python, etc.) libraries automatically compose with the application without having to think about any of that -- I'd say Java was a revolution for code reuse not because of OO but because of GC.

All the brain melting stuff Rustifarians have to deal with get exponentially harder when you mesh systems together which puts a break on what people can accomplish with it. (Though I guess you can interconnect islands with RC)

Poor conceptualization. C is transitional from ancient language specs like PL/I that were often unimplementable and modern specifications that succeed consistently like Common Lisp, Java and Python. There's a strange circularity in the K&R book that made me struggle with the book in high school and if you look at books like Imperfect C++ that circularity remains with the symptom that there are always important words that are used long before they are defined (RAII is chanted over and over as if it was some magic spell!) Modern language specs show you can start from primitive data types and build everything up in a logical order but I've never seen it done with C.

In normal programming languages you can parse first and deal with the symbol table later but the definition of typedef means the grammar depends on whether or not a symbol has been used, so weirdly the parser has to have access to the symbol table. All adds up to C being the language that puts the C in Cthulhu.

(For all it's faults though, C succeeded where PL/I failed as a portable systems language you could write an OS in)

I write C for the AVR8 Arduino even though it burns me up that C is moving the stack pointer around and doing other meaningless activities to support calling conventions for small programs I write that have no recursive functions and don't need it. I have something that's basically a display controller for persistence of vision displays that composites sprites that are stored in program memory following linked lists. A program like this could use entirely global variables and it would be simple, clear and efficient. Sure it doesn't scale but the thing has just 2K of RAM and I am not writing big programs for it. I'd be happier with the code in assembly, the only reason I haven't rewritten it is that I might want to run it on an ARM or ESP32 board and the C would be portable modulo just a little Harvard architecture weirdness.

  • ActorNightly a day ago

    >The ideal reusable library in C would be able to use a buffer from the application if the application had one ready.

    So....mempool?

ecesena 17 hours ago

I'm back working full time in C, I'll provide two examples (links in my profile so this comment doesn't become too overly promotional).

SoloKeys is an open security key. The 1st version of its firmware was in C, and while building the v2 hardware we decided to also rebuild the firmware in Rust.

- Could have we started in Rust? I'm not sure because embedded Rust was very immature when we started SoloKeys.

- Was it good to move to Rust? Probably, some of the bugs we had in C were the classical overflows that in theory shouldn't happen in Rust. The future will tell.

In summary: SoloKeys is my personal "classical example" of starting in C for various reasons and upgrading to Rust to improve safety. Let's look at the opposite example. :)

Firedancer is a reimplementation of Solana validator in C. The original validator, now called Agave, is in Rust. You can think of: 1) a Solana validator as a single-machine database, and 2) the Solana blockchain as a distributed system where all nodes run the db and independently (try to) run the same transactions on some initial state to (hopefully) produce the same resulting state. By this definition our goal is to make a second implementation of a db node in a different language, we could have chosen any language.

C was chosen for performance, but it's NOT the superficial "C is faster than rust", in fact the goal is to make the Rust implementation reach comparable performance. The difference is in the mental model, where we look at how data flows in the (single machine) system and try to optimize it at all levels. We had C code already for other projects that we repurposed it for Firedancer.

The experience of porting Rust to C has its own complexities, I'm going to list a few. Note that these are not critics to the language nor to the developers. If Solana started in C and we were porting it to Rust we would find some of the same issues and probably more. I'll try to highlight my learnings in case someone else has to port a project from language A to B -- granted our case might be a bit special because the goal is to run both in parallel and achieve consensus while often times B is going to replace A.

- In C we don't use malloc. Pretty much everything is pre-allocated and thus has known bounds. In Rust we see a lot of Vec or other dynamic structures whose bounds are not clear. In some cases these led to discovering DoS vulnerabilities. Note that this is not C, this is our mental model that translates to using C without mallocs.

Learning 1: always know your (memory) bounds.

- Rust `?` operator is very nice for Rust, very bad for someone who's trying to read the code, especially if they (i.e. me) have to match all error cases. It's very easy to miss some. Together with dyn err, it can easily make error propagation kind of random. We're spending a considerable amount of dev time and fuzzing resources not just to avoid issues with C, but to make sure we always return the same value/error in C as in Rust.

Learning 2: specify error behavior (e.g. expected errors, precedence of errors), not just the correct flow.

- Tests. This is my pet peeve, I apologize in advance. Many unit tests generate their input and then test a specific behavior. I work primarily in cryptography so let me make an example in this area: we want to test a signature verification. Bad test IMO: create a signature, then check that verify is successful. Good test IMO: there's a bunch of bytes (that we can create once by running the signature code, that's totally fine), check that the verify is successful. Think to what happens if you change the signature scheme. The first test is still passing, the second will highlight that there was a change and requires new bytes. I have countless examples of breaking changes in the Rust project that required major rewrites in C, that no one in the "Rust team" even thought they could cause any issue because unit tests are not highlighting any problem.

Learning 3: always have at least 1 test that's just a bunch of constant bytes in input.

  • purple-leafy 15 hours ago

    Thanks for the detailed write up, really interesting examples you have there - sounds like you are working on some interesting projects!

    These are all good things to keep in mind for me. I haven't had any experience with Rust yet, and only a few months with C.

revskill a day ago

Time for redefinition of old concepts. We need safe by construction concepts, no ub, no such thing as a raw pointer,...

AnimalMuppet 2 days ago

I'm in embedded, so it's pretty much C, C++, Rust, or Ada. But Ada never really caught on, and I'm working on an older code base.

So, it's either C or C++. C++ because destructors and all that they enable. (Plus access control.)