[HN Gopher] Love C, hate C: Web framework memory problems
___________________________________________________________________
Love C, hate C: Web framework memory problems
Author : OneLessThing
Score : 75 points
Date : 2025-10-10 03:39 UTC (19 hours ago)
(HTM) web link (alew.is)
(TXT) w3m dump (alew.is)
| jacquesm wrote:
| There are many, many more such issues with that code. The person
| that posted it is new to C and had an AI help them to write the
| code. That's a recipe for disaster, it means the OP does not
| actually understand what they wrote. It looks nice but it is full
| of footguns and even though it is a useful learning exercise it
| also is a great example of why it is better run battle tested
| frame works than to inexpertly roll your own.
|
| As a learning exercise it is useful, but it should never see
| production use. What is interesting is that the apparent
| cleanliness of the code (it reads very well) is obscuring the
| fact that the quality is actually quite low.
|
| If anything I think the conclusion should be that AI+novice does
| not create anything that is useable without expert review and
| that that probably adds up to a net negative other than that the
| novice will (hopefully) learn something. It would be great if
| someone could put in the time to do a full review of the code, I
| have just read through it casually and already picked up a couple
| of problems, I'm pretty sure that if you did a thorough job of it
| there would be many more.
| drnick1 wrote:
| > What is interesting is that the apparent cleanliness of the
| code (it reads very well) is obscuring the fact that the
| quality is actually quite low.
|
| I think this is a general feature and one of the greatest
| advantages of C. It's simple, and it reads well. Modern C++ and
| Rust are just horrible to look at.
| messe wrote:
| I slightly unironically believe that one of the biggest
| hindrances to rust's growth is that it adopted the :: syntax
| from C++ rather than just using a single . for namespacing.
| jacquesm wrote:
| I believe that the fanatics in the rust community were the
| biggest factor. They turned me off what eventually became a
| decent language. There are some language particulars that
| were strange choices, but I get that if you want to start
| over you will try to get it all right this time around. But
| where the Go authors tried to make the step easy and kept
| their ego out of it, it feels as if the rust people aimed
| at creating a new temple rather than to just make a new
| tool. This created a massive chicken-and-the-egg problem
| that did not help adoption at all. Oh, and toolchain speed.
| For non-trivial projects for the longest time the rust
| toolchain was terribly slow.
|
| I don't remember any other language's proponents actively
| attacking the users of other programming language.
| imtringued wrote:
| Software vulnerabilities are an implicit form of
| harassment.
| messe wrote:
| I'm hoping that's meant to satirise the rust community,
| because it's horseshit like this that makes a sizeable
| subset of rust evangelists unbearable.
| 01HNNWZ0MV43FF wrote:
| > I don't remember any other language's proponents
| actively attacking the users of other programming
| language.
|
| I just saw someone on Hacker News saying that Rust was a
| bad language because of its users
| jacquesm wrote:
| Yawn. Really, if you have nothing to say don't do it
| here.
| lelanthran wrote:
| > I believe that the fanatics in the rust community were
| the biggest factor.
|
| I second this; for a few years it was impossible to have
| any sort of discussion on various programming places when
| the topic was C: the conversation would get quickly
| derailed with accusations of "dinosaur", etc.
|
| Things have gone quiet recently (last three years,
| though) and there have been much fewer derailments.
| citbl wrote:
| The safer the C code, the more horrible it starts looking
| though... e.g. my_func(char msg[static 1])
| uecker wrote:
| Compared to other languages, this is still nice.
| jacquesm wrote:
| It is - like everything else - nice because you, me and
| lots of others are used to it. But I remember starting
| out with C and thinking 'holy crap, this is ugly'. After
| 40+ years looking at a particular language it no longer
| looks ugly simply because of familiarity. But to a
| newcomer C would still look quite strange and
| intimidating.
|
| And this goes for almost all programming languages. Each
| and every one of them has warts and issues with syntax
| and expressiveness. That holds true even for the most
| advanced languages in the field, Haskell, Erlang, Lisp
| and more so for languages that were originally designed
| for 'readability'. Programming is by its very nature more
| akin to solving a puzzle than to describing something.
| The puzzle is to how to get the machine to do something,
| to do it correctly, to do it safely and to do it
| efficiently, and all of those while satisfying the
| constraint of how much time you are prepared (or allowed)
| to spend on it. Picking the 'right' language will always
| be a compromise on some of these, there is no programming
| language that is perfect (or even just 'the best' or
| 'suitable') for all tasks, and there are no programming
| languages that are better than any other for any subset
| of all tasks until 'tasks' is a very low number.
| moefh wrote:
| I don't understand why people think this is safer, it's the
| complete opposite.
|
| With that `char msg[static 1]` you're telling the compiler
| that `msg` can't possibly be NULL, which means it will
| optimize away any NULL check you put in the function. But
| it will still happily call it with a pointer that could be
| NULL, with no warnings whatsoever.
|
| The end result is that with an "unsafe" `char *msg`, you
| can at least handle the case of `msg` being NULL. With the
| "safe" `char msg[static 1]` there's nothing you can do --
| if you receive NULL, you're screwed, no way of guarding
| against it.
|
| For a demonstration, see[1]. Both gcc and clang are passed
| `-Wall -Wextra`. Note that the NULL check is removed in the
| "safe" version (check the assembly). See also the gcc
| warning about the "useless" NULL check ("'nonnull' argument
| 'p' compared to NULL"), and worse, the lack of warnings in
| clang. And finally, note that neither gcc or clang warn
| about the call to the "safe" function with a pointer that
| could be NULL.
|
| [1] https://godbolt.org/z/qz6cYPY73
| lelanthran wrote:
| > I don't understand why people think this is safer, it's
| the complete opposite.
|
| Yup, and I don't even need to check your godbolt link -
| I've had this happen to me once. It's the implicit
| casting that makes it a problem. You cannot even typedef
| it away as a new type (the casting still happens).
|
| The real solution is to create and use opaque types. In
| this case, wrapping the `char[1]` in a struct would
| almost certainly generate compilation errors if any
| caller passed the wrong thing in the `char[1]` field.
| OneLessThing wrote:
| I agree that it reads really well which is why I was also
| surprised the quality is not high when I looked deeper. The
| author claims to have only used AI for the json code, so your
| conclusion may be off, it could just be a novice doing novice
| things.
|
| I suppose I was just surprised to find this code promoted in my
| feed when it's not up to snuff. And I'm not hating, I do in
| fact love the project idea.
| lifthrasiir wrote:
| Yeah, I recently wrote a moderate amount of C code [1] entirely
| with Gemini and while it was much better than what I initially
| expected I needed a constant steering to avoid inefficient or
| less safe code. It needed an extensive fuzzing to get the
| minimal amount of confidence, which caught at least two serious
| problems---seriously, it's much better than most C programmers,
| but still.
|
| [1] https://github.com/lifthrasiir/wah/blob/main/wah.h
| jacquesm wrote:
| I've been doing this the better part of a lifetime and I
| still need to be careful so don't feel bad about it. Just
| like rust has an 'unsafe' keyword I realize _all_ of my code
| is potentially unsafe. Guarding against UB, use-after-free,
| array overruns and so on is a lot of extra work and you only
| need to slip up once to have a bug, and if you 're unlucky
| something exploitable. You get better at this over the years.
| But if I know something needs to be bullet proof the C
| compiler would not be my first tool of choice.
|
| One good defense is to reduce your scope continuously. The
| smaller you make your scope the smaller the chances of
| something escaping your attention. Stay away from globals and
| global data structures. Make it impossible to inspect the
| contents of a box without going through a well defined
| interface. Use assertions liberally. Avoid fault propagation,
| abort immediately when something is out of the expected
| range.
| uecker wrote:
| I strategy that helps me is just not use open-coded pointer
| arithmetic or string manipulation but encapsulate those
| behind safe bounds-checked interfaces. Then essentially
| only life-time issues remain and for those I usually do
| have a simple policy and clearly document any exception. I
| also use signed integers and the sanitizer in trapping
| mode, which turns any such issue I may have missed into a
| run-time trap.
| OneLessThing wrote:
| This is why I love C. You can build these guard rails at
| exactly the right level for you. You can build them all
| the way up to CPython and do garbage collection and
| constant bounds checking. Or keep them at just raw
| pointer math. And everywhere in between. I like your
| approach. The downside being that there are probably
| 100,000+ bespoke implementations of similar guard rails
| where python users for example all get them for free.
| jacquesm wrote:
| It definitely is a lot of freedom.
|
| But the lack of a good string library is by itself
| responsible for a very large number of production issues,
| as is the lack of foresight regarding de-referencing
| pointers that are no longer valid. Lack of guardrails
| seems to translate in 'do what you want' not necessarily
| 'build guard rails at the right level for you', most
| projects simply don't bother with guardrails at all.
|
| Rust tries to address a lot of these issues, but it does
| so by tossing out a lot of the good stuff as well and
| introducing a whole pile of new issues and concepts that
| I'm not sure are an improvement over what was there
| before. This creates a take-it-or-leave it situation, and
| a barrier to entry. I would have loved to see that guard
| rails concept extended to the tooling in the form of
| compile time flags resulting in either compile time
| flagging of risky practices (there is some of this now,
| but I still think it is too little) and runtime errors
| for clear violations.
|
| The temptation to 'start over' is always there, I think C
| with all of its warts and shortcomings is not the best
| language for a new programmer to start with if they want
| to do low level work. At the same time, I would - still,
| maybe that will change - hesitate to advocate for rust,
| it is a massive learning curve compared to the kind of
| appeal that C has for a novice. I'd probably recommend Go
| or Java over both C and rust if you're into imperative
| code and want to do low level work. For functional
| programming I'd recommend Erlang (if only because of the
| very long term view of the people that build it) or
| Clojure, though the latter seems to be on its retour.
| OneLessThing wrote:
| This is exactly my problem with LLM C code, lack of
| confidence. On the other hand, when my projects get big
| enough to the point where I cannot keep the code base
| generally loaded into my brains cache they eventually get to
| the point where my confidence comes from extensive testing
| regardless. So maybe it's not such a bad approach.
|
| I do think that LLM C code if made with great testing tooling
| in concert has great promise.
| jacquesm wrote:
| That generalizes to anything LLM related.
| lelanthran wrote:
| > It needed an extensive fuzzing to get the minimal amount of
| confidence, which caught at least two serious problems---
| seriously, it's much better than most C programmers, but
| still.
|
| How are you doing your fuzzing? You need either valgrind (or
| compiler sanitiser flags) in the loop for a decent level of
| confidence.
| lifthrasiir wrote:
| The "minimal" amount of confidence, not a decent level of
| confidence. You are completely right that I need much more
| to establish anything higher than that.
| citbl wrote:
| The irony is also that AI could have been used to audit the
| code and find these issues. All the author had to do was to
| question.
| nurettin wrote:
| > should never see production use.
|
| I have an issue with high strung opinions like this. I wrote
| plenty of crappy delphi code while learning the language that
| saw production use and made a living from it.
|
| Sure, it wasn't the best experience for users, it took years to
| iron out all the bugs and there was plenty of frustration
| during the support phase (mostly null pointer exceptions and db
| locks in gui).
|
| But nobody would be better off now if that code never saw
| production use. A lot of business was built around it.
| zdragnar wrote:
| Buggy code that just crashes or produces incorrect results
| are a whole different category. In C a bug can compromise a
| server and your users. See the openssl heart bleed
| vulnerability as a prime example.
|
| Once upon a time, you could put up a relatively vulnerable
| server, and unless you got a ton of traffic, there weren't
| too many things that would attack it. Nowadays, pretty much
| anything Internet facing will get a constant stream of
| probes. Putting up a server requires a stricter mindset than
| it used to.
| jacquesm wrote:
| There are minimum standards for deployment to the open web. I
| think - and you're of course entirely free to have a
| different opinion - that those are not met with this code.
| nurettin wrote:
| Yes, I have lots of opinions!
|
| I guess the question at spotlight is: At what point would
| your custom server's buffer overflow when reading a header
| matter and would that bug even exist at that point?
|
| Could a determined hacker get to your server without even
| knowing what weird software you cooked up and how to
| exploit your binary?
|
| We have a lot of success stories born from bad code. I mean
| look at Micro$oft.
|
| Look at all the big players like discord leaking user
| credentials. Why would you still call out the little fish?
|
| Maybe I should create a form for all these ahah.
| lelanthran wrote:
| I can't completely blame the language here: anyone "coding" in a
| language new to them using an LLM is going to have real problems.
| OneLessThing wrote:
| It's funny the author says this was 90% written without AI, and
| that AI was mostly used for the json code. I think they're just
| new to C.
|
| Trust me I love C. Probably over 90% of my lifetime code has
| been written in C. But python newbies don't get their web
| frameworks stack smashed. That's kind of nice.
| lelanthran wrote:
| > But python newbies don't get their web frameworks stack
| smashed. That's kind of nice.
|
| Hah! True :-)
|
| The thing is, smashed stacks are _difficult_ to exploit
| deterministically or automatically. Even heartbleed, as
| widespread as it was, was not a guaranteed RCE.
|
| OTOH, an exploit in a language like Python is almost
| certainly going to be easier to exploit deterministically.
| Log4j, for example, was a _guaranteed_ exploit and the skill
| level required was basically _" Create a Java object"_.
|
| This is because of the ease with which even very junior
| programmers can create something that appears to run and work
| and not crash.
| uyzstvqs wrote:
| It's a double-sided coin. LLMs are probably the best way to
| learn programming languages right now. But if you vibecode in a
| programming language that you don't understand, it's going to
| be a disaster sooner or later.
|
| This is also the reason why AI will not replace any actual jobs
| with merit.
| messe wrote:
| > Another interesting choice in this project is to make lengths
| signed:
|
| There are good reasons for this choice in C (and C++) due to
| broken integer promotion and casting rules.
|
| See: "Subscripts and sizes should be signed" (Bjarne Stroustrup)
| https://open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1428r0...
|
| As a nice bonus, it means that ubsan traps on overflow (unsigned
| overflows just wrap).
| uecker wrote:
| I do not agree that the integer promotion or casting (?) rules
| are broken in C. That some people make mistakes because they do
| not know them is a different problem.
|
| The reason you should make length signed is that you can use
| the sanitizer to find or mitigate overflow as you correctly
| observe, while unsigned wraparound leads to bugs which are
| basically impossible to find. But this has nothing to do with
| integer promotion and wraparound bugs can also create bugs in -
| say - Rust.
| OneLessThing wrote:
| It's interesting to hear these takes. I've never had problems
| catching unsigned wrap bugs with plain old memory sanitizers,
| though I must admit to not having a lot of experience with
| ubsan in particular. Maybe I should use it more.
| jacquesm wrote:
| I've had some fun reviewing some very old code I wrote
| (1980's) to see what it looked like to me after such a long
| time of gaining experience. It's not unlike what the OP did
| here, it reads cleanly but I can see many issues that
| escaped my attention at the time. I always compared C with
| a very fast car: you can take some corners on two wheels
| but if you make a habit of that you're going to end up in a
| wall somewhere. That opinion has not changed.
| uecker wrote:
| I think the correct comparison is a sharp knife. It is
| extremely useful and while there is a risk it is fully
| acceptable. The idea that we should all use plastic
| knifes because there are often accidents with knifes is
| wrong and so is the idea that we use should abandon C
| because of memory safety. I follow computer security
| issues for several decades, and while I think we should
| have memory safety IMHO the push and arguments are
| completely overblown - and they are especially not worth
| the complexity and issues of Rust. I never was personally
| impacted by a security exploit caused by memory safety or
| know anybody in my personal vicinity who was. I know many
| cases where people where affected by _other_ kinds of
| security issues. So I think those are what we should
| focus on first. And having timely security updates is a
| hell lot more important than memory safety, so I am not
| happy that Rust now makes this harder.
| jacquesm wrote:
| That's an interesting point you are making there. The
| most common exploits are of the human variety. Even so it
| is probably a good idea to minimize the chances of all
| kinds of exploits. One other problem - pet peeve of mine
| - is that instead of giving people _just_ security
| updates manufacturers will happily include a whole bunch
| of new and 'exciting' stuff in their updates that in
| turn will (1) introduce new security issues and (2) will
| inevitably try to extract more money from the updaters.
| This is extremely counterproductive.
| simonask wrote:
| I'm sorry, but there is an incredible amount of hard data
| on this, including the number of CVEs directly
| attributable to memory safety bugs. This is publicly
| available information, and we as an industry should take
| it seriously.
|
| I don't mean to be disrespectful, but this cavalier
| attitude towards it reads like vaccine skepticism to me.
| It is not serious.
|
| Programming can be inconsequential, but it can also be
| national security. I know which engineers I would trust
| with the latter, and they aren't the kind who believe
| that discipline is "enough".
| jacquesm wrote:
| So what do you propose to do?
| simonask wrote:
| I propose that we start taking the appropriate amount of
| professional responsibility.
|
| That includes being honest about the actual costs of
| software when you don't YOLO the details. Zero UB is
| table stakes now - it didn't use to be, but we don't live
| in that world anymore.
|
| It's totally fine to use C or whatever language for it,
| but you are absolutely kidding yourself if you think the
| cost is less than at least an order of magnitude higher
| than the equivalent code written in Rust, C#, or any
| other language that helps you avoid these bugs. Rust even
| lets you get there at zero performance cost, so we're
| down to petty squabbles about syntax or culture - not
| serious.
| jacquesm wrote:
| > I propose that we start taking the appropriate amount
| of professional responsibility.
|
| I agree. For me that means: software _engineering_ should
| start taking the same attitude to writing software that
| structural engineers bring to the table when they talk
| about bridges, buildings and other structures that will
| have people 's lives depending on them. I'm not sure how
| we're going to make rings out of bits but we need to
| realize - continuously - that the price of failure is
| often paid in blood, or in the best case with financial
| loss and usually not by us. And in turn we should be
| enabled to impose that same ethic on management, because
| more often than not that's the root cause of the problem.
|
| > That includes being honest about the actual costs of
| software when you don't YOLO the details.
|
| Does that include development cost?
|
| Maintenance costs?
|
| Or just secondary costs?
|
| Why the focus on costs?
|
| > Zero UB is table stakes now - it didn't use to be, but
| we don't live in that world anymore.
|
| This is because 'Rust and C# exist'? Or is it because
| Java, Erlang, Visual Basic, Lisp etc exist?
|
| > It's totally fine to use C or whatever language for it,
| but you are absolutely kidding yourself if you think the
| cost is less than at least an order of magnitude higher
| than the equivalent code written in Rust, C#, or any
| other language that helps you avoid these bugs.
|
| We were talking about responsibility first, and that goes
| well beyond just measuring 'cost'. The mistake in
| bringing cost into it is that cost is a business concept
| that is used to justify picking a particular technology
| over another. And just like security is an expense that
| doesn't show anything on the balance sheet if it works
| besides that it cost money the same goes for picking a
| programming language eco-system.
|
| So I think focusing on cost is a mistake. That just
| allows the bookkeeper to make the call and that call will
| often be the wrong one.
|
| > Rust even lets you get there at zero performance cost,
| so we're down to petty squabbles about syntax or culture
| - not serious.
|
| The debate goes a lot further than that. You have
| millions of people that are writing software every day
| that are not familiar with Rust. To get them to pick a
| managed language over what they are used to is going to
| take a lot of convincing.
|
| It starts of with ethics, and I don't think it should
| start off with picking a favorite language. You educate,
| show by example and you deliver at or below the same cost
| that those other eco-systems do and then you slowly eat
| the world because your projects are delivered on-time,
| with provably lower real world defects and hopefully at a
| lower cost.
|
| And then I really couldn't care what language was picked,
| in the rust world that translates into 'anything but C'
| because that is perceived to be the enemy somehow, which
| is strange because there are many alternatives to rust
| that are perfectly suitable, have much higher mind share
| already.
|
| C is - even today - at 10x the popularity that rust is,
| it will take a massive amount of resources to switch
| those people over, and likely it will take more than one
| generation. In the meantime all of the C code in the
| world will have to be maintained, which means there is
| massive job security for people learning C. For people
| learning rust to the exclusion of learning C that
| situation is far worse. This needs to be solved.
|
| These are not 'petty squabbles' about syntax or culture.
| They are the harsh reality of the software development
| world at large, which has seen massive projects deployed
| at scale developed with those really bad languages full
| of undefined behavior (well, that's at least one thing
| that Assembly Language has going for it, as long as the
| CPU does what it says in the book undefined behavior
| doesn't exist). People are going to point at that and say
| 'good enough'. And they see all those memory overflows,
| CVEs etc as a given, and they realize that in spite of
| all of those the main vector for security issues is
| people, and configuration mistakes not so much the
| software itself.
|
| This is not ideal, obviously, but C, like any bad habit,
| is very hard to dislodge if your main argument is 'you
| should drop this tool because mine is better'. Then you
| need to _show_ that your tool is better, so much better
| that it negates the cost to switch. And that 's a very
| tall order, for any programming language, much more so
| for one that is struggling for adoption in the first
| place.
| simonask wrote:
| Cost is a useful metric because it reflects a number of
| relevant things: Time to develop, effort to maintain -
| yes, but also people turnover, required expertise levels,
| satisfaction, and so on. Whether or not you like it, you
| have to care about cost if you want to make rational
| decisions. I'm not talking about assigning a
| Euro/Dollar/Yuan value to each hour spent on a project,
| but you need a rough idea about the size of the time and
| energy investment you are making when starting a project.
|
| > This is because 'Rust and C# exist'? Or is it because
| Java, Erlang, Visual Basic, Lisp etc exist?
|
| Things have changed for three important reasons: (1)
| C/C++ compilers have evolved, and UB is significantly
| more catastrophic than it was in the 90s and early 00s.
| (2) As societies digitize, the stakes are higher than
| even - leaking personal data has huge legal and moral
| consequences, and system outages can have business-
| killing financial consequences. (3) There are actual,
| viable alternatives - GC is no longer a requirement for
| memory safety.
|
| > To get them to pick a managed language over what they
| are used to is going to take a lot of convincing.
|
| Perhaps you didn't mean to say so, but Rust is not a
| managed language (that's a .NET term referring to C#, F#,
| etc.).
|
| Me and other Rust users are obviously trying to convince
| even more people to use the language, and that's because
| we are having a great time over here. It's a very
| pleasant language with a pleasant community and a high
| level of technical expertise, and it allows me to get
| significantly closer to living up to my own ideals. I'm
| not making a moral argument here, trying to say that you
| or anyone is a bad person for not using Rust, but I am
| making a moral argument saying that _denying_ the huge
| cost and risk associated with developing software in C
| and C++ is bullshit.
|
| > And then I really couldn't care what language was
| picked, in the rust world that translates into 'anything
| but C' because that is perceived to be the enemy somehow,
| which is strange because there are many alternatives to
| rust that are perfectly suitable, have much higher mind
| share already.
|
| The point here is that, until Rust came along, you had
| the choice between wildly risky (but fast) C and C++
| code, or completely safe (but slow) garbage collected
| languages with heavy runtimes and significant deployment
| challenges.
|
| C is certainly not "the enemy" - I never said that, and I
| wouldn't. But that old world is gone. The excuse of
| picking risky, problem-riddled languages that we _know_
| are associated with extreme costs for reasons of
| performance no longer has any technical merit. There can
| be other reasons, but this isn 't it.
|
| > C is - even today - at 10x the popularity that rust is,
| it will take a massive amount of resources to switch
| those people over [...]
|
| It's insane to me that anyone would limit themselves to a
| single language. Every competent programmer I know knows
| at least a handful. Why are we worried about this? I'm a
| decent C programmer, and a very good C++ programmer -
| better at both because I'm also fairly good at Rust.
|
| > And they see all those memory overflows, CVEs etc as a
| given, and they realize that in spite of all of those the
| main vector for security issues is people, and
| configuration mistakes not so much the software itself.
|
| "Pobody's nerfect." I'm sorry, I really dislike this
| attitude. We can't let the fact that security is hard, or
| that perfection is unattainable, be an excuse to deliver
| more crap.
|
| > This is not ideal, obviously, but C, like any bad
| habit, is very hard to dislodge if your main argument is
| 'you should drop this tool because mine is better'
|
| Again, that's not my argument. My argument is that you
| should be honest about what the actual costs, or
| alternatively the actual quality.
| jacquesm wrote:
| > Cost is a useful metric because it reflects a number of
| relevant things: Time to develop, effort to maintain -
| yes, but also people turnover, required expertise levels,
| satisfaction, and so on. Whether or not you like it, you
| have to care about cost if you want to make rational
| decisions. I'm not talking about assigning a
| Euro/Dollar/Yuan value to each hour spent on a project,
| but you need a rough idea about the size of the time and
| energy investment you are making when starting a project.
|
| You are missing the cost to switch and that's a massive
| one and the one that I think most parties are using to
| decide whether or not to stick with what they know or to
| try something that is new to them. If you have a team of
| 50 embedded C++ developers and a deadline 'let's use
| rust' is a gamble very few managers will make.
|
| > Things have changed for three important reasons: (1)
| C/C++ compilers have evolved, and UB is significantly
| more catastrophic than it was in the 90s and early 00s.
|
| That depends on what industry you are looking at. For
| instance, in aviation the cost of undefined behavior,
| crashing software or wrong calculations was always that
| high. The difference is that in that industry (and a
| handful of others) there is enough budget to do it right
| resulting in far fewer in production issues than what we
| have come to accept in the 'always online, auto-update'
| world. That whole attitude is as much or more to blame
| for this than any particular language.
|
| > (2) As societies digitize, the stakes are higher than
| even - leaking personal data has huge legal and moral
| consequences, and system outages can have business-
| killing financial consequences.
|
| Show me the names of the businesses that have died
| because of data leaks or UB. See, the problem is that for
| those businesses it usually is just a speedbump. They
| don't care and no matter what the size of the breach the
| consequences are usually minor.
|
| The employee sticking a USB drive found on the street
| into their laptop causing a cryptolocker incident is a
| much more concrete problem.
|
| > (3) There are actual, viable alternatives - GC is no
| longer a requirement for memory safety.
|
| GC is a convenience, and if you're going to switch
| languages you might as well pick one that is is more
| convenient. Java for instance is suitable now for 90% or
| so of the use cases where C or C++ would be your only
| option 15 years ago.
|
| > Perhaps you didn't mean to say so, but Rust is not a
| managed language (that's a .NET term referring to C#, F#,
| etc.).
|
| I know, but Java, Lisp and so on _are_ managed languages,
| and they offer both safety _and_ convenience. Rust only
| offers safety, other than that it is only marginally more
| convenient than C and some would argue less so.
|
| > Me and other Rust users are obviously trying to
| convince even more people to use the language, and that's
| because we are having a great time over here.
|
| Show, don't tell.
|
| > It's a very pleasant language with a pleasant community
| and a high level of technical expertise, and it allows me
| to get significantly closer to living up to my own
| ideals.
|
| Yes, but those are _your_ ideals, which don 't
| necessarily overlap with mine. I don't particularly care
| about one programming language or another, I've learned
| enough of them by now to know that _all_ of them have
| their limitations, their warts, their good bits and their
| bad bits. I also know that the size of the eco-system is
| a large function in whether or not I 'll be able to get
| through the day in a productive way.
|
| > I'm not making a moral argument here, trying to say
| that you or anyone is a bad person for not using Rust,
| but I am making a moral argument saying that denying the
| huge cost and risk associated with developing software in
| C and C++ is bullshit.
|
| See, your use of the word 'bullshit' triggers me in a way
| that you probably do not intend, but it is exactly that
| attitude that turns me off the language that you would
| like me to switch to. I don't particularly see that huge
| cost and risk as applied to myself because I'm not
| currently writing code that is going to be part of some
| network service. If I see an embedded shop doing their
| work in Rust then I'm happy because I can ignore at least
| one small aspect of the source of bugs in such software.
| But there are plenty remaining and Rust - no matter what
| you think - is not a silver bullet for all of the things
| that can go wrong with low level software. There are
| other, better alternatives for most of those
| applications, I'd be more inclined to use Java or Erlang
| if those are available, and Go if they are not. The speed
| at which I can develop software is a massive factor in
| that whole 'cost' evaluation for me.
|
| > The point here is that, until Rust came along, you had
| the choice between wildly risky (but fast) C and C++
| code, or completely safe (but slow) garbage collected
| languages with heavy runtimes and significant deployment
| challenges.
|
| That just isn't true. There are more languages besides
| Rust that allow for low level and fast work. Go for
| instance is an excellent contender. And for long running
| processes Java is excellent, it is approaching C levels
| of throughput and excels at networked services.
|
| > C is certainly not "the enemy" - I never said that, and
| I wouldn't. But that old world is gone.
|
| Sorry, but this is not a realistic stance. That old world
| is not gone, and it is likely here to stay for many more
| decades. There is so much inertia here in terms of
| invested capital that you can't just make declarations
| like these and expect to be taken serious.
|
| > The excuse of picking risky, problem-riddled languages
| that we know are associated with extreme costs for
| reasons of performance no longer has any technical merit.
| There can be other reasons, but this isn't it.
|
| Do you realize that this is just your opinion and not a
| statement of fact?
|
| > It's insane to me that anyone would limit themselves to
| a single language.
|
| 'Insane' is another very loaded word. Is this really the
| kind of language you want to be using while advocating
| for Rust? There are many programmers that learn one eco
| system well enough to carve out a career for themselves,
| and I'm not going to be the one to judge them for that.
| I'm not one of them, but I can see how it happens and I
| would definitely not label everybody that's not a
| polyglot as not entirely right in the head.
|
| > Every competent programmer I know knows at least a
| handful.
|
| I know some _very_ competent programmers that only know
| one. But they know that one better than I know any of the
| ones that I 'm familiar with. For instance, I know a guy
| that decided early on that if nobody wants to work on
| COBOL projects that that is exactly what he's going to
| do: become a world class expert in COBOL to help maintain
| all that old stuff. At a price. He's making very good
| money with that, far more than he'd have ever made by
| going with something more popular. I know plenty of Java
| only programmers and a couple that have decided that
| python is all they need. That's _their right_ and it isn
| 't up to me to look down on them or call them incompetent
| because they can do something that I apparently can't:
| focus, and get really good at one thing.
|
| > Why are we worried about this? I'm a decent C
| programmer, and a very good C++ programmer - better at
| both because I'm also fairly good at Rust.
|
| I would not label myself as 'very good' in any language,
| I always hope to get better and in spite of doing this
| for 4+ decades I have never felt that I was 'good
| enough'.
|
| > "P[sic]obody's nerfect." I'm sorry, I really dislike
| this attitude.
|
| Again, why the antagonism. We have many different classes
| of issues, and depending on the context some of them may
| not be a problem at all. I've built stuff in _JavaScript_
| because it was the most suitable for the job. But I stay
| the hell away from node and anything associated with it
| because I don 't consider myself qualified to audit all
| of the code that could be pulled in through a dependency.
| And that's a good chunk of this: just know your
| limitations, and realize that not just 'nobody's perfect'
| but also that _you yourself_ are not perfect and more
| than likely to mess up when you go into territory that is
| unfamiliar to you.
|
| > We can't let the fact that security is hard, or that
| perfection is unattainable, be an excuse to deliver more
| crap.
|
| Ok. So now you are labeling what other people produce as
| 'crap'. This isn't helping.
|
| > Again, that's not my argument. My argument is that you
| should be honest about what the actual costs, or
| alternatively the actual quality.
|
| So I'm not honest. If you are wondering what I meant when
| I wrote earlier that it is the attitude of some of the
| Rust advocates that turns me off then here in this thread
| you have a very nice example of that. All of this
| pontification and emotionally laden language serves
| nobody, least of all Rust.
|
| If you want to win people over try the following:
|
| - refrain from insulting your target audience
|
| - respect the fact that your opinions are just that
|
| - understand that there may be factors outside of your
| view that are part of the decision making process
|
| - understand that you may not have a complete
| understanding of the problem domain or the restrictions
| involved (is a variation on the previous one)
|
| - try to not use emotional language to make your point
|
| - showing beats telling any day of the week
| uecker wrote:
| GCC's sanitizer does not catch unsigned wraparound. But the
| bigger problem is that a lot of code is written where it
| assumes that unsigned wraps around and this is ok. So you
| you would use a sanitizer you get a lot of false positives.
| For signed overflow, one can always consider this a bug in
| portable C.
|
| Of course, if you consistently treat unsigned wraparound as
| a bug in your code, you can also use a sanitizer to screen
| for it. But in general I find it more practical to use
| signed integers for everything except for modular
| arithmetic where I use unsigned (and where wraparound is
| then expected and not a bug)
| messe wrote:
| I meant implicit casting, but I guess that really falls under
| promotion in most cases where it's relevant here (I'm on a
| train from Aarhus to Copenhagen right now to catch a flight,
| and I've slept considerably less than usual, so apologies if
| I'm making some slight mistakes).
|
| The issues really arise when you mix signed/unsigned
| arithmetic and end up promoting everything to signed
| unexpectedly. That's usually "okay", as long as you're not
| doing arithmetic on anything smaller than an int.
|
| As an aside, if you like C enough to have opinions on
| promotion rules then you might enjoy the programming language
| Zig. It's around the same level as C, but with much nicer
| ergonomics, and overflow traps by default in
| Debug/ReleaseSafe optimization modes. If you want explicit
| two's complement overflow it has +%, *% and -% variants of
| the usual arithmetic operations, as well as saturating +|,
| *|, -| variants that clamp to [minInt(T), maxInt(T)].
|
| EDIT to the aside: it's also true if you hate C enough to
| have opinions on promotion rules.
| jacquesm wrote:
| Yes, this is one of the more subtle pitfalls of C. What
| helps is that in most contexts the value of 2 billion is
| large enough that a wraparound would be noticed almost
| immediately. But when it isn't then it can lead to very
| subtle errors that can propagate for a long time before
| anything goes off the rails that is noticed.
| uecker wrote:
| I prefer C to Zig. IMHO all the successor languages throw
| out the baby with the bathwater and add unnecessary
| complexity. But Zig is much better than Rust, but, still, I
| would never use it for a serious project.
|
| The "promoting unexpectedly" is something I do not think
| happens if you know C well. At least, I can't remember ever
| having a bug because of this. In most cases the promotion
| prevents you from having a bug, because you do not get
| unexpected overflow or wraparound because your type is too
| small.
|
| Mixing signed and unsigned is problematic, but I see issues
| mostly in code from people who think they need to use
| unsigned when they shouldn't because they heard signed
| integers are dangerous. Recently I saw somebody "upgrading"
| a C code basis to C++ and also changing all loop variables
| to size_t. This caused a bug which he blamed on working on
| the "legacy C code" he is working on, although the original
| code was just fine. In general, there are compiler warnings
| that should catch issues with sign for conversions.
| lelanthran wrote:
| > Recently I saw somebody "upgrading" a C code basis to
| C++ and also changing all loop variables to size_t. This
| caused a bug which he blamed on working on the "legacy C
| code" he is working on, although the original code was
| just fine.
|
| I had the same experience about 10 years back when a
| colleague "upgrade" code from using size_t to `int`; on
| that platform (ATMEGA or XMEGA, not too sure now) `int`
| was too small, overflowed and bad stuff happened in the
| field.
|
| The only takeaway is "don't needlessly change the size
| and sign of existing integer variables".
| Sukera wrote:
| Could you expand on how these wraparound bugs happen in Rust?
| As far as I know, integer overflow panics (i.e. aborts) your
| code when compiled in debug mode, which I think is often used
| for testing.
| 01HNNWZ0MV43FF wrote:
| > That some people make mistakes because they do not know
| them is a different problem.
|
| We can argue til we're blue in the face that people should
| just not make any mistakes, but history is against us -
| People will always make mistakes.
|
| That's why surgeons are supposed to follow checklists and
| count their sponges in and out
| bringbart wrote:
| >while unsigned wraparound leads to bugs which are basically
| impossible to find.
|
| What?
|
| unsigned sizes are way easier to check, you just need one
| invariant:
|
| if(x < capacity) // good to go
|
| Always works, regardless how x is calculated and you never
| have to worry about undefined behavior when computing x. And
| the same invariant is used for forward and backward loops -
| some people bring up i >= 0 as a problem with unsigned, but
| that's because you should use i < n for backward loops as
| well, The One True Invariant.
| user____name wrote:
| I just put assertions to check the ranges of all sizes and
| indices upon function entry, doubles as documentation, and I
| mostly don't have to worry about signedness as a result.
| kstenerud wrote:
| Yup, unsigned math is just nasty.
|
| Actually, unchecked math on an integer is going to be bad
| regardless of whether it's signed or unsigned. The difference
| is that with signed integers, your sanity check is simple and
| always the same and requires no thought for edge cases:
| `if(index < 0 || index > max)`. Plus ubsan, as mentioned above.
|
| My policy is: Always use signed, unless you have a specific
| reason to use unsigned (such as memory addresses).
| bringbart wrote:
| unsigned is easier: 'if(index >= max)' and has fewer edge
| cases because you don't need to worry about undefined
| behavior when computing index.
| lelanthran wrote:
| > The difference is that with signed integers, your sanity
| check is simple and always the same and requires no thought
| for edge cases: `if(index < 0 || index > max)`
|
| Wait, what? How is that easier than `if (index > max)`?
| bluetomcat wrote:
| Good C code will try to avoid allocations as much as possible in
| the first place. You absolutely don't need to copy strings around
| when handling a request. You can read data from the socket in a
| fixed-size buffer, do all the processing in-place, and then
| process the next chunk in-place too. You get predictable
| performance and the thing will work like precise clockwork.
| Reading the entire thing just to copy the body of the request in
| another location makes no sense. Most of the "nice" javaesque
| XXXParser, XXXBuilder, XXXManager abstractions seen in "easier"
| languages make little sense in C. They obfuscate what really
| needs to happen in memory to solve a problem efficiently.
| 01HNNWZ0MV43FF wrote:
| Can you do parsing of JSON and XML without allocating?
| bluetomcat wrote:
| Yes, you can do it with minimal allocations - provided that
| the source buffer is read-only or is mutable but is unused
| later directly by the caller. If the buffer is mutable, any
| un-escaping can be done in-place because the un-escaped
| string will always be shorter. All the substrings you want
| are already in the source buffer. You just need a growable
| array of pointer/length pairs to know where tokens start.
| gritzko wrote:
| Yep, no problem. In place parsing only requires a stack.
| Stack length is the maximum JSON nesting allowed. I have a C
| dialect exactly like that.
| veqq wrote:
| Of course. You can do it in a single pass/just parse the
| token stream. There are various implementations like:
| https://zserge.com/jsmn/
| andrepd wrote:
| It requires manual allocation of an array of tokens. So it
| needs a backing "stack vector" of sorts.
|
| And what about escapes?
| Ygg2 wrote:
| Theoretically yes. Practically there is character escaping.
|
| That kills any non-allocation dreams. Moment you have "Hi
| \uxxxx isn't the UTF nice?" you will probably have to
| allocate. If source is read-only you have to allocate. If
| source is mutable you have to waste CPU to rewrite the
| string.
| lelanthran wrote:
| > Moment you have "Hi \uxxxx isn't the UTF nice?" you will
| probably have to allocate.
|
| Depends on what you are doing with it. If you aren't
| displaying it (and typically you are not in a server
| application), you don't _need_ to unescape it.
| mpyne wrote:
| And this is indeed something that the C++ Glaze library
| supports, to allow for parsing into a string_view
| pointing into the original input buffer.
| deaddodo wrote:
| I'm confused why this would be a problem. UTF-8 and UTF-16
| (the only two common unicode subsets) are a maximum of 4
| bytes wide (and, most commonly, 2 in English text). The
| ASCII representation you gave is 6-bytes wide. I don't know
| of many ASCII unicode representations that have less
| bytewidth than their native Unicode representation.
|
| Same goes for other characters such as \n, \0, \t, \r, etc.
| All half in native byte representation.
| topspin wrote:
| > Practically there is character escaping
|
| The voice of experience appears. Upvoted.
|
| It is conceivable to deal with escaping in-place, and thus
| remain zero-alloc. It's hideous to think about, but I'll
| bet someone has done it. Dreams are powerful things.
| lelanthran wrote:
| > Can you do parsing of JSON and XML without allocating?
|
| If the source JSON/XML is in a writeable buffer, with some
| helper functions you can do it. I've done it for a few small-
| memory systems.
| lock1 wrote:
| Why does "good" C have to be zero alloc? Why should "nice"
| javaesque make little sense in C? Why do you implicitly assume
| performance is "efficient problem solving"?
|
| Not sure why many people seem fixated on the idea that using a
| programming language must follow a particular approach. You can
| do minimal alloc Java, you can simulate OOP-like in C, etc.
|
| Unconventional, but why do we need to restrict certain
| optimizations (space/time perf, "readability", conciseness,
| etc) to only a particular language?
| bluetomcat wrote:
| Because in C, every allocation incurs a responsibility to
| track its lifetime and to know who will eventually free it.
| Copying and moving buffers is also prone to overflows, off-
| by-one errors, etc. The generic memory allocator is a smart
| but unpredictable complex beast that lives in your address
| space and can mess your CPU cache, can introduce undesired
| memory fragmentation, etc.
|
| In Java, you don't care because the GC cleans after you and
| you don't usually care about millisecond-grade performance.
| jstimpfle wrote:
| No. Look up Arenas. In general group allocations to avoid
| making a mess.
| lelanthran wrote:
| > Why does "good" C have to be zero alloc?
|
| GP didn't say "zero-alloc", but "minimal alloc"
|
| > Why should "nice" javaesque make little sense in C?
|
| There's little to no indirection in idiomatic C compared with
| idiomatic Java.
|
| Of course, in both languages you can write unidiomatically,
| but that is a great way to ensure that bugs get in and never
| get out.
| lelanthran wrote:
| > Good C code will try to avoid allocations as much as possible
| in the first place.
|
| I've upvoted you, but I'm not so sure I agree though.
|
| Sure, each allocation imposes a new obligation to track that
| allocation, but on the downside, passing around already-
| allocated blocks imposes a new burden for each call to ensure
| that the callees have the correct permissions (modify it,
| reallocate it, free it, etc).
|
| If you're doing any sort of concurrency this can be hard to
| track - sometimes it's easier to simply allocate a new block
| and _give_ it to the callee, and then the caller can forget all
| about it (callee then has the obligation to free it).
| 1718627440 wrote:
| To reduce the amount of allocation instead of:
| struct parsed_data * = parse (...); struct
| process_data * = process (..., parsed_data); struct
| foo_data * = do_foo (..., process_data);
|
| you can do parse (...) { ...
| process (...); ... } process
| (...) { ... do_foo (...);
| ... }
|
| It sounds like violating separation of concerns at first, but
| it has the benefit, that you can easily do procession and
| parsing in parallel, and all the data can become readonly.
| Also I was impressed when I looked at a call graph of this,
| since this essentially becomes the documentation of the whole
| program.
| jqpabc123 wrote:
| Reads like an indictment of vibe coding.
|
| LLMs are fundamentally probabilistic --- not deterministic.
|
| This basically means that anything produced this way is highly
| suspect. And this framework is an example.
| erichocean wrote:
| Give Fil-C a try, the speed hit is pretty minimal and you get
| full memory safety.
|
| https://fil-c.org/
| Karrot_Kream wrote:
| Wow this is really cool, I'd never seen this before. Thanks!
| dang wrote:
| Recent and related:
|
| _Show HN: I built a web framework in C_ -
| https://news.ycombinator.com/item?id=45526890 - Oct 2025 (208
| comments)
| yipikaya wrote:
| As an aside, it's amusing that it took 25 years for C coders to
| embrace the C99 named struct designator feature:
| HttpParser parser = { .isValid = true,
| .requestBuffer = strdup(request), .requestLength =
| strlen(request), .position = 0, };
|
| All the kids are doing it now!
| 1718627440 wrote:
| This is nice for constant data, but strdup can return NULL
| here, which is again never checked.
|
| > it took 25 years for C coders to embrace the C99 named struct
| designator feature
|
| Not sure if this actually true, but this is kind of the feature
| of C, 20 years old code or compiler is supposed to work just
| fine, so you just wait for some time to settle things. For fast
| and shiny, there is Javascript.
| ge96 wrote:
| Long as you allocate me, it's alright
___________________________________________________________________
(page generated 2025-10-10 23:00 UTC)