I'm kinda in the opposite camp. After doing a bunch of VB in my tweens and teens, I learned Java, C, and C++ in college, settling on mostly C for personal and professional projects. I became a core developer of Xfce and worked on that for 5 years.
Then I moved into backend development, where I was doing all Java, Scala, and Python. It was... dare I say... easy! Sure, these kinds of languages bring with them other problems, but I loved batteries-included standard libraries, build systems that could automatically fetch dependencies -- and oh my, such huge communities with open-source libraries for nearly anything I could imagine needing. Even if most of the build systems (maven, sbt, gradle, pip, etc.) have lots of rough edges, at least they exist.
Fast forward 12 years, and I find myself getting back in to Xfce. Ugh. C is such a pain in the ass. I keep reinventing wheels, because even if there's a third-party library, most of the time it's not packaged on many of the distros/OSes our users use. Memory leaks, NULL pointer dereferences, use-after-free, data races, terrible concurrency primitives, no tuples, no generics, primitive type system... I hate it.
I've been using Rust for other projects, and despite it being an objectively more difficult language to learn and use, I'm still much more productive in Rust than in C.
I think Rust is harder to learn, but once you grok it, I don't think it's harder to use, or at least to use correctly. It's hard to write correct C because the standard tooling doesn't give you much help beyond `-Wall`. Rust's normal error messages are delightfully helpful. For example, I just wrote some bad code and got:
--> src/main.rs:45:34
|
45 | actions.append(&mut func(opt.selected));
| ---- ^^^^^^^^^^^^ expected `&str`, found `String`
| |
| arguments to this function are incorrect
|
help: consider borrowing here
|
45 | actions.append(&mut func(&opt.selected));
|
I even had to cheat a little to get that far, because my editor used rust-analyzer to flag the error before I had the chance to build the code.
Also, I highly recommend getting into the habit of running `cargo clippy` regularly. It's a wonderful tool for catching non-idiomatic code. I learned a lot from its suggestions on how I could improve my work.
> I think Rust is harder to learn, but once you grok it, I don't think it's harder to use, or at least to use correctly. It's hard to write correct C because the standard tooling doesn't give you much help beyond `-Wall`.
When I say Rust is harder to use (even after learning it decently well), what I mean is that it's still easier to write a pile of C code and get it to compile than it is to write a pile of Rust code and get it to compile.
The important difference is that the easier-written C code will have a bunch of bugs in it than the Rust code will. I think that's what I mean when I say Rust is harder to use, but I'm more productive in it: I have to do so much less debugging when writing Rust, and writing and debugging C code is more difficult and takes up more time than writing the Rust code (and doing whatever less debugging is necessary there).
> Also, I highly recommend getting into the habit of running `cargo clippy` regularly. It's a wonderful tool for catching non-idiomatic code.
That's a great tip, and I usually forget to do so. On a couple of my personal projects, I have a CI step that fails the build if there are any clippy messages, but I don't use it for most of my personal projects. I do have a `cargo fmt --check` in my pre-commit hooks, but I should add clippy to that as well.
If you're using VS Code then you can add `"rust-analyzer.check.command": "clippy"` to your `settings.json`. I assume there's a similar setting for rust-analyzer in other editors.
That's a fair distinction. Basically, it's easier to write C that compiles than Rust that compiles, but it's harder to write correct C than correct Rust.
Regarding Clippy, you can also crank it up with `cargo clippy -- -Wclippy::pedantic`. Some of the advice at that level gets a little suspect. Don't just blindly follow it. It offers some nice suggestions though, like:
warning: long literal lacking separators
--> src/main.rs:94:22
|
94 | if num > 1000000000000 {
| ^^^^^^^^^^^^^ help: consider: `1_000_000_000_000`
|
Rust allows to provide more information about types (generic types, pointer usage) and checks it, while in C you have to rely on doc comments and checking the code manually. Or am I wrong and C allows to specify pointer nullability, pointer ownership and array bounds?
None of those things feature in any problem I deal with on a daily basis, whatever language I use.
So for example today I dealt with a synchronization issue. This turned out to not be a code bug but a human misunderstanding of a protocol specification saga, which was not possible to code into a type system of any sort. The day before was a constraint network specification error. In both cases the code was entirely irrelevant to the problem.
Literally all I deal with are human problems.
My point is Rust doesn't help with these at all, however clever you get. It is no different to C, but C will give you a superset of vulnerabilities on top of that.
Fundamentally Rust solves no problems I have. Because the problems that matter are human ones. We are too obsessed with the microscopic problems of programming languages and type systems and not concentrating on making quality software which is far more than just "Rust makes all my problems go away" because it doesn't. It kills a small class of problems which aren't relevant to a lot of domains.
(incidentally the problems above are implemented in a subset of c++)
> So for example today I dealt with a synchronization issue. This turned out to not be a code bug but a human misunderstanding of a protocol specification saga, which was not possible to code into a type system of any sort.
Maybe not in a reasonable language no, but there are advances in type systems that are making ever larger classes of behaviours encodable into types. For example, algebraic effects (can this function throw, call a remote service etc)
Vulnerabilities are bugs, so the C code will have more bugs than the Rust program.
You might say that the C and Rust code will have the same number of logic errors, but I'm not convinced that's the case either. Sure, if you just directly translate the C to Rust, maybe. But if you rewrite the C program in Rust while making good use of Rust's type system, it's likely you'll have fewer logic errors in the Rust code as well.
Rust has other nice features that will help avoid bugs you might write in a C program, like most Result-returning functions in the stdlib being marked #[must_use], or match expressions being exhaustive, to name a couple things.
> most Result-returning functions in the stdlib being marked #[must_use]
Actually it's a bit cleverer than that, and some people might benefit from knowing this. The Result type itself is marked #[must_use]. If you're writing a Goat library and you are confident that just discarding a Goat is almost always a mistake regardless of the context in which they got a Goat you too should mark your Goat type #[must_use = "this `Goat` should be handled properly according to the Holy Laws of the Amazing Goat God"] and now everybody is required to do that or explicitly opt out even for their own Goat code.
Obviously don't do this for types which you can imagine reasonable people might actually discard, only the ones where every discard is a weird special case.
Types I like in Rust which help you avoid writing errors the compiler itself couldn't possibly catch:
Duration - wait are these timeouts in seconds or milliseconds? It's different on Windows? What does zero mean, forever or instant ?
std::cmp::Ordering - this Doodad is Less than the other one
OwnedFd - it's "just" a file descriptor, in C this would be an integer, except, this is always a file descriptor, it can't be "oops, we didn't open a file" or the count of lines, or anything else, we can't Add these together because that's nonsense, they're not really integers at all.
How can it not be true? One of the primary features of the rust compiler is enforcing memory safety at compile-time. C doesn't have anything like that. There are an entire class of bugs that are impossible to implement in rust.
> it's still easier to write a pile of C code and get it to compile than it is to write a pile of Rust code and get it to compile.
As someone who is more familiar with Rust than C: only if you grok the C build system(s). For me, getting C to build at all (esp. if I want to split it up into multiple files or use any kind of external library) is much more difficult than doing the same in Rust.
> Also, I highly recommend getting into the habit of running `cargo clippy` regularly.
You can also have that hooked up to the editor, just like `cargo check` errors. I find this to be quite useful, because i hace a hard time getting into habits, especially for thing that i'm not forced to do in some way. It's important that those Clippy lints are shown as soft warnings instead of hard errors though, as otherwise they'd be too distracting at times.
* Rust errors can be equally unhelpful. Also, the error you posted is hands down awful. It doesn't tell you what went wrong, and it's excessively naive to rely on compiler to offer a correct fix in all but the most trivial cases. When errors happen, it's a consequence of an impasse, a logical contradiction: two mutually exclusive arguments have been made: a file was assumed to exist, but was also assumed not to exist -- this is what's at the core of the error. The idiotic error that Rust compiler gave you doesn't say what were the assumptions, it just, essentially, tells you "here's the error, deal with it".
* In Rust, you will have to deal with a lot of unnecessary errors. The language is designed to make its users create a host of auxiliary entities: results, options, futures, tasks and so on. Instead of dealing with the "interesting" domain objects, the user of the language is mired in the "intricate interplay" between objects she doesn't care about. This is, in general, a woe of languages with extensive type systems, but in Rust it's a woe on a whole new level. Every program becomes a Sisyphean struggle to wrangle through all those unnecessary objects to finally get to write the actual code. Interestingly though, there's a tendency in a lot of programmers to like solving these useless problems instead of dealing with the objectives of their program (often because those objectives are boring or because programmers don't understand them, or because they have no influence over them).
I don't follow your first point—the compiler is pointing out exactly what the problem is (the argument has the incorrect type) and then telling you what you likely wanted to do (borrow the String). What would you see as a more helpful error message in this case?
The compiler says "expected X, but found Y". I don't know how to interpret this: is the type of the thing underlined with "^^^" X or Y? "Expected" and "found" are just like "up" and "down" in space: they are meaningless if you don't know what the compiler expects (and why should it?).
What it needs to say is something along the lines of "a function f is defined with type X, but is given an argument of type Y": maybe the function should be defined differently, maybe the argument needs to change -- it's up to the programmer to decide.
I dunno, I feel like if you've used a compiler regularly, "expected X, but found Y" is a pretty common idiom/shorthand that people understand. Your wordier version of that feels unnecessary to me.
C is a low level language and deals with things close to the metal. It's probably not fun to write a large business app in barebones C but you having control over low level things makes other things possible and very fast too. Depending on the type of problem you have use the appropriate and favorite language.
Since it's underlining code you wrote, it must be "found" that is highlighted, not "expected". Much like up and down, gravity exists to ground all of us in the same direction.
I'm over here with TTS: Underlining in a terminal rarely translates to audio. It isn't the only consideration that needs to be made, when making things clear.
The biggest problem with C is that doesn't even have enough features to help you build the features and abstractions you need and want.
For example with C++ the language offers enough functionality that you can create abstractions at any level, from low level bit manipulation to high level features such as automatic memory management, high level data objects etc.
With C you can never escape the low level details. Cursed to crawl.
Back in 1994/95, I wrote an API, in C, that was a communication interface. We had to use C, because it was the only language that had binary/link compatibility between compilers (the ones that we used).
We designed what I call "false object pattern." It used a C struct to simulate a dynamic object, complete with function pointers (that could be replaced, in implementation), and that gave us a "sorta/kinda" vtable.
Worked a charm. They were still using it, 25 years later.
That said, I don't really miss working at that level. I have been writing almost exclusively in Swift, since 2014.
> We designed what I call "false object pattern." It used a C struct to simulate a dynamic object, complete with function pointers (that could be replaced, in implementation), and that gave us a "sorta/kinda" vtable.
You were not alone in this. It is the basis of glib's GObject which is at the bottom of the stack for all of GTK and GNOME.
Sure, that's a pretty common pattern in use in C to this day. It's a useful pattern, but it's still all manual. Forget to fill in a function pointer in a struct? Crash. At least with C++ it will fail to compile if you don't implement a method that you have to implement.
You still need to think about error handling, and it's not standardized because everyone else will also have to think about it ad hoc.
You'll also still need to think about when to copy and move ownership, only without a type system to help you tell which is which, and good luck ensuring resources are disposed correctly (and only once) when you can't even represent scoped objects. `goto` is still the best way to deal with destructors, and it still takes a lot of boilerplate.
Of course you can. It's quite the opposite actually. The downside is that in C you have to code a bunch of abstractions _yourself_. See how large projects like the Linux kernel make extensive use of macros to implement an object system.
i never really understand why these get compared. i wouldn't expect that much overlap in the audiences
zig seems like someone wanted something between C and "the good parts" of C++, with the generations of cruft scrubbed out
rust seems like someone wanted a haskell-flavoured replacement for C++, and memory-safety
i would expect "zig for C++" to look more like D or Carbon than rust. and i'd expect "rust for C" to have memory safety and regions, and probably steal a few ocaml features
The single best feature (and I would say the _core_ feature separating it from C) that C++ has to offer is RAII and zig does not have that. So I don’t know which good parts of C++ they kept. Zig is more of its own thing, and they take from wherever they like, not just C++.
I really wish that were true but it isn’t. Modern C++ templates/constexpr are much more powerful and expressive than any Zig comptime equivalent.
The power and expressiveness of the C++ compile-time capabilities are the one thing I strongly miss when using other languages. The amount of safety and conciseness those features enable makes not having them feel like a giant step backward. Honestly, if another systems language had something of similar capability I’d consider switching.
I have written a lot of Zig comptime code and ended up finding the opposite. In C++ I find I have to bend over backward to get what I want done, often resulting in insane compile times. I've used metaprogramming libraries like Boost Hana before to have some more ergonomics, but even that I would consider inferior to comptime.
Out of curiosity, do you happen to have any examples of what you describe, where C++ is more powerful and expressive than Zig?
If it looks anything like what I read in "Modern C++ Design" 20+ years ago then I'll pass. That book made me realize the language wasn't for me anymore.
It looks nothing like C++ decades ago, it is effectively a completely different language. I found C++ unusable before C++11, and even C++11 feels archaic these days. Idiomatic C++20 and later is almost a decent language.
Zig has defer, which is arguably way simpler. Is there something RAII can do that defer can't?
Not everyone likes RAII by itself. Allocating and deallocating things one at a time is not always efficient. That is not the only way to use RAII but it's the most prevalent way.
defer can't emulate the destructors of objects that outlives their lexical scope. Return values and heap objects are examples of these since they outlive the function they were created in. defer only supports enqueuing actions at lexical boundaries.
If you destroy an object that outlives the lexical scope it was created in, then you have to clean up manually.
I have, and I do find Zig impressive, but it doesn't go far enough for me. I don't want a "better C", I want a better systems language that can also scale up for other uses.
I like strong, featureful type systems and functional programming; Zig doesn't really fit the bill for me there. Rust is missing a few things I want (like higher-kinded types; GATs don't go far enough for me), but it's incredible how they've managed to build so many zero- and low-cost abstractions and make Rust feel like quite a high-level language sometimes.
Rust is not a modern C++, their core models are pretty different. Both Rust and C++ can do things the other can’t do. C++ is a more focused on low-level hyper-optimized systems programming, Rust is a bit higher level and has stronger guardrails but with performance closer to a classic systems language.
I do think Zig is a worthy successor to C and isn’t trying to be C++. I programmed in C for a long time and Zig has a long list of sensible features I wish C had back then. If C had been like Zig I might never have left C.
What do you consider the difference in their core models.
Rust and C++ both use RAII, both have a strong emphasis on type safety, Rust just takes that to the extreme.
I would like to even hope both believe in 0 cost abstractions, which contrary to popular belief isn't no cost, but no cost over doing the same thing yourself.
In many cases it's not even 0 cost, it's negative cost since using declarative programming can allow the compiler to optimise in ways you don't know about.
I Really want to use Rust. But Rust has so many std functions that kind of abstracting
how does it work under the hood
For Ex: I still has no idea what clone() does, how does it interact with memory, on heap or stack
, does it create a new instance, or just modify metadata of that object. Sometime creating a new instance
is a big no-no because it takes a lot of memory.
Same thing with "ownership transfer", is variable freed at that moment, etc.
I bet i could find answers on internet, but rust has like 500 std functions, so the task is tedious
What is your current language of choice, both C and C++ have the same problems as what you just described.
Regarding ownership transfer it is even worse in C, what if you forget, after moving an object out of a variable, to set that variable to NULL, then free that variable, that's a use after free. At least in C++ you have move semantics although it is still error prone. In rust it's a compiler error.
Copy and Clone is the same, and both are opt-in for your own types, by default the only options are move or reference for your own types, in C and C++ by default it is copy, which again leads to use after free in the situations you complained about.
I feel if these are your complaints you will actually benefit from spending some time in Rust.
If your preferred language is higher level languages with GC that is reference by default I encourage you to try any of the systems level programming languages, the things you complain about are things that are important for a language to have constructs for, reference semabtics by default causes many issues, and becomes untenable in the parallel world we live in.
Well, i dont use C++ much(i'm FW engineer, most of my stuff is in C).
The std in C is simple and explicit. For Ex: I can make an educated guess how memcpy() work by looking at its signature. It takes pointer to src and destination, and size, so i can guess it does not allocate any new memory(or if it has, it has to be some kind of optimization reason).
Another example is strstr(), it returns pointer to a piece of memory i provided to it, so i can safely do some pointer math with the return value.
It's true that i do not spend much time in Rust, so maybe i'm missing some fundamental things. I guess my mistake is trying to apply my knowledge in C to rust.
But still, it's kind of irritating now knowing (or guessing) how does function work just by looking at at its signature.
Isn't guessing what a function does based purely on its name pretty risky? There's usually nuance, so if I'm not already familiar with a function, I'm looking up its docs at least once.
Similarly, I went from writing a lot of C to Python, and I appreciate both of them for almost opposite reasons. I ended up finding I like Cython quite a bit, even though the syntax leaves much to be desired. The power, performance, and flexibility of C combined with the batteries included nature of Python is a match made in heaven.
You're also still very much free to write either language purely, and "glue" them together easily using Cython.
I will say the more recent additions to C++ at least have solved many of my long standing issues with that C-variant. Most of it was stuff that was long overdue. Like string formatting or a thread safe println. But even some of the stuff I didn’t think I would love has been amazing. Modules. Modules bro. Game changer. I’m all in. Honestly C++ is my go to for anything that isn’t just throw away again. Python will always be king of the single use scripts.
The problem is that they are _additions_, C++ has such absurd sprawl. The interactions between everything in this massive sprawl is quite difficult to grasp
I lost interest in keeping up with C++'s advances more than a decade ago.
The problem is that I want a language where things are safe by default. Many of the newer stuff added in C++ makes things safe, perhaps even to the level of Rust's guarantees -- but that's only if you use only these new things, and never -- even by accident -- use any of the older patterns.
I'd rather just learn a language without all that baggage.
I suffered writing those for many years. I finally simply learned not to do them anymore. Sort of like there's a grain of sand on the bottom of my foot and the skin just sort of entombed it in a callous.
I've seen you make these kinds of comments before on other articles. Please stop. Not everyone is perfect and can forevermore avoid making any mistakes. I strongly suspect your opinion of your skill here is overinflated. Even if it isn't, and you really are that good, everyone cannot be in the top 0.00001% of all programmers out there, so your suggestion to "simply" learn not to make mistakes is useless.
This all just comes off incredibly arrogant, if I'm being honest.
I think a more charitable interpretation of what he said was, "after sufficient practice, I became good enough to start avoiding those pitfalls."
It's not all that different from learning any challenging task. I can't skateboard to save my life, but the fact that people can do it well is both admirable and the result of hundreds or thousands of hours of practice.
Skilled people can sometimes forget how long it took to learn their talent, and can occasionally come off as though the act is easy as a result. Don't take it too harshly.
He's not making a comment about everyone, it's a specific comment about how often long time C programmers make basic mistakes after a million SLOC or so.
In this instance Walter is correct - the mistakes he listed are very rarely made by experienced C programmers, just as ballet dancers rarely trip over their own feet walking down a pavement.
The problem of those errors being commonplace in those that are barely five years in to C coding and still have another five to go before hitting the ten year mark still exists, of course.
But it's a fair point that given enough practice and pain those mistakes go away.
> just as ballet dancers rarely trip over their own feet walking down a pavement
What about about walking down a busy construction site? The most charitable and correct interpretation I can think of is "I'm a professional. Seatbelts and OSHA destroy my productivity."
> What about about walking down a busy construction site?
Coordinated people with some years of experience pay attention to the ground and overhead cranes and conveyor belts and survive walking through construction sites, mine sites, aviation hangers, cattle yards, musters, et al on a routine basis. I'm 60+ and have somehow navigated all those environs - including C for critical system control.
These are dangerous environments. No one denies this. It's still true that the longer you inhabit such spaces the safer your innate learned behaviour is.
C has seatbelts and OSHA - valgrind, et al tools abound for sanity checking.
Walter's GP statement is literally little more than "eventually you grow out of making the simple basic maistakes" - eventually, after some years of practice - which is a real problem with C, it takes time to not make the basic mistakes. After all that, there's always room, in C, in Rust, whatever, to make non basic non obvious mistakes.
> After all that, there's always room, in C, in Rust, whatever, to make non basic non obvious mistakes.
Correct, I guess. The number of relatively obvious mistakes should decrease with experience. And it stands to reason that eventually it settles near zero for some part of developer community.
How close to zero and which part of community? Statistic is scarce.
> C has seatbelts and OSHA - valgrind, et al tools abound for sanity checking.
Optional tools with no general enforcement. That is more like elective vaccination or travel advisories. That is, no, no seatbelts and no OSHA.
I'd imagine the main way one reduces instances of these mistakes is to restrict resource ownership into certain patterns which have a clear place for freeing, and rules that ensure it's always reached, and only once.
There are many approaches depending on the type of program or suite of programs being built.
Always pairing the creation of free() code and functions with every malloc() is one discipline.
Another, for a class of C utilities, is to never free() at all .. "compute anticipated resource limits early, malloc and open pipes in advance, process data stream and exit when done" works for a body of cases.
In large C projects of times past it's often the case that resource management, string handling, etc are isolated and handled in dedicated sub sections that resemble the kinds of safe handling methods baked into modern 'safe' languges.
You come off as incredibly arrogant too, you just don't realise it because you have the current mainstream opinion and the safety of a crowd.
Do you know how fucking obnoxious it is when 200 people like you come into every thread to tell 10 C or Javascript developers that they can't be trusted with the languages and environments they've been using for decades? There are MILLIONS of successful projects across those two languages, far more than Rust or Typescript. Get a fucking grip.
That is very interesting. You have quite the resume too. While I've dabbled in nearly everything, I'm a day to day pro C# developer and I absolutely love it. I've never been upset or had a complaint. If I were forced off for some reason, I'd just go to Typescript. I can't imagine using C. Perhaps with some form of AI valgrind. The problems C solved are just not relevant any longer, and it remains entrenched in 2025. Rust with AI analysis will be amazing to see the results of.
I fully understand that sentiment. For several years now, I have also felt the strong urge to develop something in pure C. My main language is C++, but I have noticed over and over again that I really enjoy using the old C libraries - the interfaces are just so simple and basic, there is no fluff. When I develop methods in pure C, I always enjoy that I can concentrate 100% on algorithmic aspects instead of architectural decisions which I only have to decide on because of the complexity of the language (C++, Rust). To me, C is so attractive because it is so powerful, yet so simple that you can hold all the language features in your head without difficulty.
I also like that C forces me to do stuff myself. It doesn't hide the magic and complexity. Also, my typical experience is that if you have to write your standard data structures on your own, you not only learn much more, but you also quickly see possibly performance improvements for your specific use case, that would have otherwise been hidden below several layers of library abstractions.
This has put me in a strange situation: everyone around me is always trying to use the latest feature of the newest C++ version, while I increasingly try to get rid of C++ features. A typical example I have encountered several times now is people using elaborate setups with std::string_view to avoid string copying, while exactly the same functionality could've been achieved by fewer code, using just a simple raw const char* pointer.
About 16 years ago I started working with a tech company that used "C++ as C", meaning they used a C++ compiler but wrote pretty much everything in C, with the exception of using classes, but more like Python data classes, with no polymorphism or inheritance, only composition. Their classes were not to hide, but to encapsulate. Over time, some C++ features were allowed, like lambdas, but in general we wrote data classed C - and it screamed, it was so fast. We did all our own memory management, yes, using C style mallocs, and the knowledge of what all the memory was doing significantly aided our optimizations, as we targeted to be running with on cache data and code as much as possible. The results were market leading, and the company's facial recognition continually lands in the top 5 algorithms at the annual NIST FR Vendor test.
Funnily enough, 16 years ago, I too was in exactly this type of company. C++, using classes, inheritance only for receiving callbacks, most attributes were public (developers were supposed to know how not to piss outside the bowl), pthreads with mutexed in-memory queues for concurrency, no design patterns (yes we used globals instead of Singleton) etc. So blazingly fast we were measuring latencies in sub 100-microseconds. Now, when modern developers say something is "blazingly fast" when it's sub-second, I can only shake my head in disbelief.
Yes, very similar. We had pthreaded mutexed queues too, and measured timings with clock_gettime() with CLOCK_MONOTONIC. Our facial template match runs at 25M compares per second per core, and simply keeping that pipeline fed required all kinds of timing synchronizations.
The only community I know that produces developers that know this kind of stuff intimately are console game programmers, and then only the people responsible for maintaining the FPS at 60. I expect the embedded community knows this too, but is too small for me to know many of them to get a sense of their general technical depth.
Slightly better ergonomics I suppose. Member functions versus function pointers come to mind, as do references vs pointers (so you get to use . instead of ->)
Yeah, slightly better ergonomics. Although we could, we simply did not use function pointers, we used member functions from the data class the data sat inside. We really tried to not focus on the language and tools, but to focus on the application's needs in the context of the problem it solves. Basically, treat the tech as a means to an end, not as a goal in itself.
Try doing C with a garbage collector ... it's very liberating.
Do `#include <gc.h>` then just use `GC_malloc()` instead of `malloc()` and never free. And add `-lgc` to linking. It's already there on most systems these days, lots of things use it.
You can add some efficiency by `GC_free()` in cases where you're really really sure, but it's entirely optional, and adds a lot of danger. Using `GC_malloc_atomic()` also adds efficiency, especially for large objects, if you know for sure there will be no pointers in that object (e.g. a string, buffer, image etc).
There are weak pointers if you need them. And you can add finalizers for those rare cases where you need to close a file or network connection or something when an object is GCd, rather than knowing programmatically when to do it.
But simply using `GC_malloc()` instead of `malloc()` gets you a long long way.
You can also build Boehm GC as a full transparent `malloc()` replacement, and replacing `operator new()` in C++ too.
At first I really liked this idea, but then I realised the size of stack frames is quite limited, isn't it? So this would work for small data but perhaps not big data.
Yea, usually the stack ulimit is only a few KiB for non-root processes by default on linux.
It is easy enough to increase, but it does add friction to using the software as it violates the default stack size limit on most linux installs. Not even sure why stack ulimit is a thing anymore, who cares if the data is on the stack vs the heap?
In theory, this is a compiler implementation detail. The compiler may chose to put large stacks in the heap, or to not even use a stack/heap system at all. The semantics of the language are independent of that.
In practice, stack sizes used to be quite limited and system-dependent. A modern linux system will give you several megabites of stack by default (128MB in my case, just checked in my linux mint 22 wilma). You can check it using "ulimit -all", and you can change it for your child processes using "ulimit -s SIZE_IN_KB". This is useful for your personal usage, but may pose problems when distributing your program, as you'll need to set the environment where your program runs, which may be difficult or impossible. There's no ergonomical way to do that from inside your C program, that I know of.
Its a giant peeve of mine that automatic memory management, in the C language sense of the resource being freed at the end of its lexical scope, is tied to the allocation being on the machine stack which in practice may have incredibly limited size. Gar! Why!?
Ackshually, it has nothing to do with the C language. It's an implementation choice by some compilers. A conforming implementation could give you the whole RAM and swap to your stack.
It isn't a practical pattern for anything beyond the most trivial applications. Consider what this would look like if you tried to write a text editor, for instance - if a user types a new line of text, where is the memory for that allocated?
The problem is that regardless of the amount of confrontation it does not have an answer for any infinite run time event-loop based program, other than "allocate all of memory into a buffer at startup and implement your own memory manager inside that".
Which just punts the problem from a mature and tested runtime library to some code you just make up on the spot.
>Try doing C with a garbage collector ... it's very liberating.
Doing that means that I lose some speed and I will have to wait for GC collection.
Then why shouldn't I use C# which is more productive and has libraries and frameworks that comes with batteries included that help me build functionality fast.
I thought that one of the main points of using C is speed.
I think one of the nice things about C is that since the language was not designed to abstract e.g.: heap is that it is really easy to replace manual memory management with GC or any other approach to manage memory, because most APIs expects to be called with `malloc()` when heap allocation is needed.
I think the only other language that has a similar property is Zig.
> Odin is a manual memory management based language. This means that Odin programmers must manage their own memory, allocations, and tracking. To aid with memory management, Odin has huge support for custom allocators, especially through the implicit context system.
Interesting that I was thinking of a language that combined Zig and Scala to allocate memory using implicits and this looks exactly what I was thinking.
Not that I actually think this is a good idea (I think the explicitly style of Zig is better), but it is an idea nonetheless.
I like the idea of using C++ as C. I began disliking OOP, inheritance and encapsulation, heavy usage of GoF patterns and even SOLID. They promise easy to understand, easy to follow, easy to maintain, easy to change, easy to extend code and a good productivity but the effect is contrary, most of the times.
I like functional programming and procedural programming. Fits better to how I think about code. Code is something that takes data and spits data. Code shouldn't be forced into emulating some real life concepts.
I never liked that you have to choose between this and C++ though. C could use some automation, but that's C++ in "C with classes" mode. The sad thing is, you can't convince other people to use this mode, so all you have is either raw C interfaces which you have to wrap yourself, or C++ interfaces which require galaxy brain to fully grasp.
I remember growing really tired of "add member - add initializer - add finalizer - sweep and recheck finalizers" loop. Or calculating lifetime orders in your mind. If you ask which single word my mind associates with C, it will be "routine".
C++ would be amazing if its culture wasn't so obsessed with needless complexity. We had a local joke back then: every C++ programmer writes heaps of C++ code to pretend that the final page of code is not C++.
I completely agree with this sentiment. That's why I wrote Datoviz [1] almost entirely in C. I use C++ only when necessary, such as when relying on a C++ dependency or working with slightly more complex data structures. But I love C’s simplicity. Without OOP, architectural decisions become straightforward: what data should go in my structs, and what functions do I need? That’s it.
The most inconvenient aspect for me is manual memory management, but it’s not too bad as long as you’re not dealing with text or complex data structures.
Agreed. C, Go, Python, and Lua are my go-to languages because of their simplicity. It's unfortunate, but in my opinion, most mainstream languages are needlessly complex.
In my experience, whether it's software architecture or programming language design, it's easy to make things complicated, but it takes vision and discipline to keep them simple.
I agree with this sentiment. My first gig was telecom, and I wrote in a pascal like language called CHILL, but found out my forte was debugging and patching and ended up doing a fair amount of assembly code that would get applied to live systems. The decade plus I spent in medical devices, I used C and assembly.
The thing is, if you own all the code and actually understand what it is supposed to do, you can write safe code.
Variety is good. I got so used to working in pure C and older C++ that for a personal project I just started writing in C, until I realised that I don't have to consider other people and compatibility, so I had a lot of fun trying new things.
> A typical example I have encountered several times now is people using elaborate setups with std::string_view to avoid string copying, while exactly the same functionality could've been achieved by fewer code, using just a simple raw const char* pointer.
C++ can avoid string copies by passing `const string&` instead of by value. Presumably you're also passing around a subset of the string, and you're doing bounds and null checks, e.g.
string_view is just a char* + len; which is what you should be passing around anyway.
Funnily enough, the problem with string view is actually C api's, and this problem exists in C. Here's a perfect example: (I'm using fopen, but pretty much every C api has this problem).
FILE* open_file_from_substr(const char* start, int len)
{
return fopen(start);
}
void open_files()
{
const char* buf = "file1.txt file2.txt file3.txt";
for (int i = 0; i += 10; ++i) // my math might be off here, apologies
{
open_file_from_substr(buf + i, buf + i + 10); // nope.
}
}
> When I develop methods in pure C, I always enjoy that I can concentrate 100% on algorithmic aspects instead of architectural decisions which I only have to decide on because of the complexity of the language
I agree this is true when you develop _methods_, but I think this falls apart when you design programs. I find that you spend as much time thinking about memory management and pointer safety as you do algorithmic aspects, and not in a good way. Meanwhile, with C++, go and Rust, I think about lifetimes, ownership and data flow.
I started programming with C a long time ago, and even now, every few months, I dream of going back to those roots. It was so simple. You wrote code, you knew roughly which instructions it translated to, and there you went!
Then I try actually going through the motions of writing a production-grade application in C and I realise why I left it behind all those years ago. There's just so much stuff one has to do on one's own, with no support from the computer. So many things that one has to get just right for it to work across edge cases and in the face of adversarial users.
If I had to pick up a low-level language today, it'd likely be Ada. Similar to C, but with much more help from the compiler with all sorts of things.
> I started programming with C a long time ago, and even now, every few months, I dream of going back to those roots. It was so simple. You wrote code, you knew roughly which instructions it translated to, and there you went!
Related-- I'm curious what percentage of Rust newbies "fighting the borrow checker" is due to the compiler being insufficiently sophisticated vs. the newbie not realizing they're trying to get Rust to compile a memory error.
Not everything can be proved at compile-time, so necessarily Rust is going to complain about things that you know can be done safely in that specific context.
For example some tree structures are famously PITA in Rust. Yes, possible, but PITA nonetheless.
I certainly spent most (95%+?) of my "fighting the borrow checker" time writing code I would never try to write in C++. A simple example is strings: I'd spend a lot of time trying to get a &str to work instead of a String::clone, where in equivalent C++ code I'd never use std::string_view over std::string - not because it would be a memory error to do so in my code as it stood, but because it'd be nearly impossible to keep it memory safe with code reviews and C++'s limited static analysis tooling.
This was made all the worse by the fact that I frequently, eventually, succeeded in "winning". I would write unnecessary and unprofiled "micro-optimizations" that I was confident were safe and would remain safe in Rust, that I'd never dare try to maintain in C++.
Eventually I mellowed out and started .clone()ing when I would deep copy in C++. Thus ended my fight with the borrow checker.
If you come from C to Rust to basically have to rewire your brain. There are some corner cases that are wrong in Rust, but mostly you have to get used to a completely new way of thinking about object lifetimes and references to objects.
On x86-type machines, you still have a decent chance, because the instructions themselves are so complicated and high-level. It's not that C is close to the metal, it's that the metal has come up to nearly the level of C!
I wouldn't dare guess what a compiler does to a RISC target.
(But yes, this was back in the early-to-mid 2000s I think. Whether that is a long time ago I don't know.)
Another way of looking at it (although, I’m not sure if I believe this, haha)—it might be easy to guess what the the C compiler will spit out, for the proprietary bytecode known as “x86.” It is hard to guess what actual machine code (uops) it will be jitted to, when it is actually compiled by the x86 virtual machine.
I'd call it a while ago, but not a long time. Long time to me is more like 70s or 80s. I was born in 1996 so likely I'm biased: "before me=long time". It would be interesting to do a study on that. Give the words, request the years, correlate with birthyear, voila
Given how fast our field grows, you might want to consider anything beyond 13 years "a long time ago", since only a tenth of us were around back then[1].
Writing user-facing applications in Swift and dropping to C and C++ when required seems to give the best of both worlds.
For me the main benefit of C and C++ is the availability of excellent and often irreplaceable libraries. With a little bridging work, these tend to just work with Swift.
Also, COBOL and FORTRAN. FORTRAN is still being developed and one of the languages supported as first class citizen by MPI.
There's a big cloud of hype at the bleeding edge, but if you dare to look beyond that cloud, there are many boring and well matured technologies doing fine.
Indeed. I'm still not entirely sure why Rust was created when we have Ada, but if I had to guess it's mainly because Rust has slightly more advanced tricks for safe memory management, and to some degree because Rust has curly braces.
My conclusion is that C is not a good basis for what Rust is trying to do. The kind of reliability Rust is trying to provide with almost no runtime overhead requires a much more complex language than C.
When Ada was first announced, I rushed to read about it -- sounded good. But so far, never had access to it.
So, now, after a long time, Ada is starting to catch on???
When Ada was first announced, back then, my favorite language was PL/I, mostly on CP67/CMS, i.e., IBM's first effort at interactive computing with a virtual machine on an IBM 360 instruction set. Wrote a little code to illustrate digital Fourier calculations, digital filtering, and power spectral estimation (statistics from the book by Blackman and Tukey). Showed the work to a Navy guy at the JHU/APL and, thus, got "sole source" on a bid for some such software. Later wrote some more PL/I to have 'compatible' replacements for three of the routines in the IBM SSP (scientific subroutine package) -- converted 2 from O(n^2) to O(n log(n)) and the third got better numerical accuracy from some Ford and Fulkerson work. Then wrote some code for the first fleet scheduling at FedEx -- the BOD had been worried that the scheduling would be too difficult, some equity funding was at stake, and my code satisfied the BOD, opened the funding, and saved FedEx. Later wrote some code that saved a big part of IBM's AI software YES/L1. Gee, liked PL/I!
When I started on the FedEx code, was still at Georgetown (teaching computing in the business school and working in the computer center) and in my appartment. So, called the local IBM office and ordered the PL/I Reference, Program Guide, and Execution Logic manuals. Soon they arrived, for free, via a local IBM sales rep highly curious why someone would want those manuals -- sign of something big?
> So, now, after a long time, Ada is starting to catch on???
Money and hardware requirements.
Finally there is a mature open source compiler, and our machines are light years beyond those beefy workstations required for Ada compilers in the 1980's.
I recently started re-reading "Programming in Ada" by J.G.P. Barnes about the original Ada. In my opinion, it was not that good of a language. Plenty of ways to trigger undefined behavior.
Where C was clearly designed to be a practical language with feedback from implementing an operating system in C. Ada lacked that kind of practical experience. And it shows.
I don't know anything about modern day Ada, but I can see why it didn't catch on in the Unix world.
I'm curious about this list, because it definitely doesn't seem that way these days. It'd be interesting to see how many of these are still possible now.
def route = fn (request) {
if (request.method == GET ||
request.method == HEAD) do
locale = "en"
slash = if Str.ends_with?(request.url, "/") do "" else "/" end
path_html = "./pages#{request.url}#{slash}index.#{locale}.html"
if File.exists?(path_html) do
show_html(path_html, request.url)
else
path_md = "./pages#{request.url}#{slash}index.#{locale}.md"
if File.exists?(path_md) do
show_md(path_md, request.url)
else
path_md = "./pages#{request.url}.#{locale}.md"
if File.exists?(path_md) do
show_md(path_md, request.url)
end
end
end
end
}
Yeah, I'm not sure a lot of people read the article. This isn't really a back to basics, going back to C, forgoing complexity type of article, but instead it's about developing a new programming language called KC3 to make use of ideas he originally developed in Lisp.
C was my first language and I quickly wrote my first console apps and a small game with Allegro. It feels incredibly simple in some aspects. I wouldn’t want to go back though. The build tools and managing dependencies feels outdated, somehow there is always a problem somewhere. Includes and the macro system feels crude. It’s easy to invoke undefined behavior and only realizing later because a different compiler version or flag now optimizes differently. Zig is my new C, includes a C compiler and I can just import C headers and use it without wrapper. Comptime is awesome. Build tool, dependency management and testing included. Cross compilation is easy. Just looks like a modern version of C. If you can live with a language that is still in development I would strongly suggest to take a look.
Otherwise I use Go if a GC is acceptable and I want a simple language or Rust if I really need performance and safety.
Java was created to solve some frequent troubles you have in C-code.
I do not want to be rude, but C has some error-prone syntax: if you forget a *, you will be in trouble.
If you do 1 byte offset error on an array in the stack, you get erratic behavior, core dump if you are lucky.
Buffer overlflows also poses security risks.
try...catch was not present on C, and it is one of the most powerful addition of C++ for code structuring.
Thread management/async programming without support from the language (which is fine, but if you see Erlang or Java, they have far more support for thread monitors).
Said that, there are very high quality library in C (pthreads, memory management and protection, lib-eventio etc) which can overcome most of its limit but... it is still error-prone
If you want to do microcontroller/embedded, I think C it still the overall best choice, supported by vendors.
Rust and Ada are probably slowly catching up.
Ironically, AT&T's monopoly actually helped the adoption of Unix, but not in an exploitative way. In 1956, AT&T was subject to a consent decree by the US government, where AT&T was allowed to maintain its phone monopoly but was not allowed to expand its market to other sectors. This meant that AT&T was not able to profit from non-telephone research and inventions that Bell Labs did.
During Unix's early days, AT&T was still under this decree, meaning that it would not sell Unix like how competitors sold their operating systems. However, AT&T licensed Unix, including its source code, to universities for a nominal fee that covered the cost of media and distribution. UC Berkeley was one of the universities that purchased a Unix licenses, and researchers there started making additions to AT&T Unix which were distributed under the name Berkeley Software Distribution (this is where BSD came from). There is also a famous book known as The Lions' Book (https://en.wikipedia.org/wiki/A_Commentary_on_the_UNIX_Opera...) that those with access to a Unix license could read to study Unix. Bootleg copies of this book were widely circulated. The fact that university students, researchers, and professors could get access to an operating system (source code included) helped fuel the adoption of Unix, and by extension C.
When the Bell System was broken up in 1984, AT&T still retained Bell Labs and Unix. The breakup of the Bell System also meant that AT&T was no longer subject to the 1956 consent decree, and thus AT&T started marketing and selling Unix as a commercial product. Licensing fees skyrocketed, which led to an effort by BSD developers to replace AT&T code with open-source code, culminating with 4.3BSD Net/2, which is the ancestor of modern BSDs (FreeBSD, NetBSD, OpenBSD). The mid-1980s also saw the Minix and GNU projects. Finally, a certain undergraduate student named Linus Torvalds started work on his kernel in the early 1990s when he was frustrated with how Minix did not take full advantage of his Intel 386 hardware.
Had AT&T never been subject to the 1956 consent decree, it's likely that Unix might not have been widely adopted since AT&T probably wouldn't have granted generous licensing terms to universities.
You can certainly do entirely absurd things in Perl.
But it is a lot easier / safer work with.
You get / can get a wealth of information when you
the wrong thing in Perl.
With C
segmentation fault is not always easy to pinpoint.
However the tooling for C, with sone if the IDEs
out there you can set breakpoints/ walk through
the code in a debugger, spot more errors during compile
time.
There is a debugger included with Perk but after trying
to use it a few times I have given up on it.
Give me C and Visual Studio when I need debugging.
On the positive side, shooting yourself in the foot with C
is a common occurrence.
I have never had a segmentation fault in Perl.
Nor have I had any problems managing the memory,
the garbage collector appears to work well.
(at least for my needs)
Except when they don’t. I’m debugging something right now that runs fine under debugging conditions and crashes with a segfault in real life. It’s not randomized memory (messed with that), it’s likely some race where the timing is changed by the debugger.
I sometimes write C recreationally. The real problem I have with it is that it's overly laborious for the boring parts (e.g. spelling out inductive datatypes). If you imagine that a large amount of writing a compiler (or similar) in C amounts to juggling tagged unions (allocating, pattern matching over, etc.), it's very tiring to write the same boilerplate again and again. I've considered writing a generator to alleviate much of the tedium, but haven't bothered to do it yet. I've also considered developing C projects by appealing to an embeddable language for prototyping (like Python, Lua, Scheme, etc.), and then committing the implementation to C after I'm content with it (otherwise, the burden of implementation is simply too high).
It's difficult because I do believe there's an aesthetic appeal in doing certain one-off projects in C: compiled size, speed of compilation, the sense of accomplishment, etc. but a lot of it is just tedious grunt work.
Yeah, I think every programmer experiences the "I should write a language" moment when the solution to the problem is abstracted to be the language itself.
What is your killer app?
What CL has to do with no one running it?
What problem you had with garbage collectors?
Why is C is the only option?
Are you sure all those RCEs because of VMs and containers and not because it all written in C?
"There are no security implications of running KC3 code" - are you sure?
I love Common Lisp, but I would definitely think twice before implementing anything I want other people to use on their machines in it. A tiny C executable is pretty nimble in comparison to anything you'll get out of Common Lisp.
I understand, it isn't that bad though: a web app of mine with dozens of dependencies and all templates and static assets is 35MB with SBCL and core compression (that includes the compiler and debugger, useful to connect to a running app and exploring its state (or even hot reloading code)). I suppose that's in the ballpark of a growing Go application. LispWorks has a tree shaker that builds a hello world in 5MB.
> Virtual machines still suck a lot of CPU and bandwidth for nothing but emulation. Containers in Linux with cgroups are still full of RCE (remote command execution) and priviledge escalation. New ones are discovered each year. The first report I got on those listed 10 or more RCE + PE (remote root on the machine). Remote root can also escape VMs probably also.
A proper virtual machine is extremely difficult to break out of (but it can still happen [1]). Containers are a lot easier to break out of. I virtual machines were more efficient in either CPU or RAM, I would want to use them more, but it's the worst of both.
I've tried, but never succeeded in doing that; the complexity eventually seeps in through the cracks.
C++'s stdlib contains a lot of convenient features, writing them myself and pretending they aren't there is very difficult.
Disabling exceptions is possible, but will come back to bite you the second you want to pull in external code.
You also lose some of the flexibility of C, unions become more complicated, struct offsets/C style polymorphism isn't even possible if I remember correctly.
> C++'s stdlib contains a lot of convenient features, writing them myself and pretending they aren't there is very difficult.
I've never understood the motivation behind writing something in C++, but avoiding the standard library. Sure, it's possible to do, but to me, they are inseparable. The basic data types and algorithms provided by the standard library are major reasons to choose the language. They are relatively lightweight and memory-efficient. They are easy to include and link into your program. They are well understood by other C++ programmers--no training required. Throughout my career, I've had to work in places where they had a "No Standard Library" rule, but that just meant they implemented their own, and in all cases the custom library was worse. (Also, none of the companies could articulate a reason for why they chose to re-implement the standard library poorly--It was always blamed on some graybeard who left the company decades ago.)
Choosing C++ without the standard library seems like going skiing, but deliberately using only one ski.
Modern C++ has goodies like consteval that are supremely useful for embedded work. STL and the rest of the stdlib on the other hand depends on heap and exceptions for error reporting which are generally a no go zone for resource constrained targets.
You can productively use C++ as C-with-classes (and templates, and namespaces, etc.) without depending on the library. That leaves you no worse off than rolling your own support code in plain C.
> Garbage collectors suck, and all my Common Lisp projects have very limited applications just because of the garbage collector.
If only a very tiny fraction of the resources effort, research, time, money etc of all ML/AI funds are directed for the best design of high performance GC, it will make a software world a much better place. The fact that we have a very few books dedicated on GC design and thousands of books now dedicated on AI/ML, it is quite telling.
For real-world example and analogy, automotive industry dedicated their resources on the best design of high performance automatic transmission and now it has a faster auto transmission than manual for rally and racing. For normal driving auto is what the default and available now, most of the cars do not sell in manual transmission version.
> Linux is written in C, OpenBSD is written in C, GTK+ is object-oriented pure C, GNOME is written in C. Most of the Linux desktop apps are actually written in plain old C. So why try harder ? I know C
C is the lingua-franca of all other programming languages including Python, Julia, Rust, etc. Period.
D language has already bite the bullet and made C built-in by natively supporting it. Genius.
D language also has GC by default for more sane and intuitive programming, it's your call. It also one of the fastest compilation time and execution time languages in existence.
From the KC3 language website, "KC3 is a programming language with meta-programmation and a graph database embedded into the language."
Why you want to have a graph database embedded into the language? Just support associative array built-in since it has been proven to be the basis of all common data representations of spreadsheet, SQL, NoSQL, matrices, Graph database, etc.
[1] Associative Array Model of SQL, NoSQL, and NewSQL Databases:
Going from mid-90s assembly to full stack dev/sec/ops, getting back to just a simple Borland editor with C or assembly code sounds like a lovely dream.
Your brain works a certain way, but you're forced to evolve into the nightmare half-done complex stacks we run these days, and it's just not the same job any more.
I still think in the right tool for the job. Trying to write some web application in C will drive me mad. Also will trying to write some low level stuff in Java.
What I've discovered is that while it does regularize some of the syntax of C, the really noticeable thing about Zig is that it feels like C with all the stuff I (and everyone else) always end up building on my own built into the language: various allocators, error types, some basic safety guardrails, and so forth.
You can get clever with it if you want -- comptime is very, very powerful -- but it doesn't have strong opinions about how clever you should be. And as with C, you end up using most of the language most of the time.
I don't know if this is the actual criterion for feature inclusion and exclusion among the Zig devs, but it feels something like "Is this in C, or do C hackers regularly create this because C doesn't have it?" Allocators? Yes. Error unions? Yes. Pattern matching facilities? Not so much. ADTs? Uh, maybe really stupid ones? Generics, eh . . . sometimes people hack that together when it feels really necessary, but mostly they don't.
Something like this, it seems to me, results in features Zig has, features Zig will never have, and features that are enabled by comptime. And it's keeping the language small, elegant, and practical. I'm a big time C fan, and I love it.
I saw mentions of Zig here often, so I decided to look at the docs to see what features it has. I had to scroll through all the docs only to find myself disappointed by the fact that Zig doesn't help with memory management in any way and advices to use comments and careful coding instead. And what adds to the disappointment is that its plus/minus operators do not catch overflow. If I wanted undefined behaviour, I could just use C/C++ as no language can compete with them in this regard.
Zig is a much simpler language than Rust. I'm a big Rust fan, but Rust is not even close to a drop-in replacement for C. It has a steep learning curve, and often requires thinking about and architecting your program much differently from how you might if you were using C.
For a C programmer, learning and becoming productive in Zig should be a much easier proposition than doing the same for Rust. You're not going to get the same safety guarantees you'd get with Rust, but the world is full of trade offs, and this is just one of them.
For me, where linked lists, graphs and other structures are a common need, zig gives me slices and deferred frees.
Rust is double expensive in this case. You have to memorize the borrow checker and be responsible for all the potential undefined behavior with unsafe code.
But I am not a super human systems programmer. Perhaps if I was the calculus would change. But personally when I have to drop down below a GC language, it is pretty close to the hardware.
Zig simply solves more of my personal pain points... but if rust matures in ways that help those I'll consider it again.
Correct me if I am wrong, but Rust at least has a borrow checker while in C (and Zig) one has to do the borrow checking in their head. If you read a documentation for C libraries, some of them mention things like "caller must free this memory" and others don't specify anything and you have to go to the source code to find out who is responsible for freeing the memory.
Rust gives two reasons for the borrow checker, iterator invalidation and use after free.
As I have always bought into
Dennis Ritchie's loop programming concepts, iterator invalidating hasn't been a problem.
Zig has defer which makes it trivial to place next to allocation, and it is released when it goes out of scope.
As building a linked list, dealing with bit fields, ARM peripherals, etc...; all require disabling the Rust borrow checker rules, you don't benefit at all from them in those cases IMHO.
It is horses for courses, and the Rust project admits they chose a very specific horse.
C is what it is, and people who did assembly on a PDP7 probably know where a lot of that is from.
I personally prefer zig to c... but I will use c when it makes the task easier.
Writing code in C is very unpleasant, verbose and repetative. For example, if I want to have a structure in C and have a way to print its contents, or free its memory and memory of nested structures, or clone it recursively, it is very difficult to make automatically. I found only two options: either write complicated macros to define the structure and functions (feels like writing a C++ compiler from scratch), or define structure in Python and generate the C code from it.
I looked at C++, but it seems that despite being more feature-rich, it also cannot auto-generate functions/methods for working with structures?
Also returning errors with dynamically allocated strings (and freeing them) makes functions bloated.
Also Gnome infrastructure (GObject, GTK and friends) requires writing so much code that I feel sorry for people writing Gnome.
Also, how do you install dependencies in C? How do you lock a specific version (or range of versions) of a dependency with specific build options, for example?
So this is a journey where starting in ruby, going through an SICP phase, and then eventually compromising that it isn't viable. it kinda seems like C is just the personal compromise of trying to maintain nerdiness rather than any specific performance needs.
I think it's a pretty normal pattern I've seen (and been though) of learning-oriented development rather than thoughtful engineering.
But personally, AI coding has pushed me full circle back to ruby. Who wants to mentally interpret generated C code which could have optimisations and could also have fancy looking bugs. Why would anyone want to try disambiguating those when they could just read ruby like English?
> But personally, AI coding has pushed me full circle back to ruby.
This happened to me too. I’m using Python in a project right now purely because it’s easier for the AI to generate and easier for me to verify. AI coding saves me a lot of time, but the code is such low quality there’s no way I’d ever trust it to generate C.
It depends on what you need the code for. If it’s something mission critical, then using AI is likely going to take more time than it saves, but for a MVP or something where quality is less important than time to market, it’s a great time saver.
Also there’s often a spectrum of importance even within a project, eg maybe some internal tools aren’t so important vs a user facing thing. Complexity also varies: AI is pretty good at simple CRUD endpoints, and it’s a lot faster than me at writing HTML/CSS UI’s (ie the layout and styling, without the logic).
If you can isolate the AI code to code that doesn’t need to be high quality, and write the code that doesn’t yourself, it can be a big win. Or if you use AI for an MVP that will be incrementally replaced by higher quality code if the MVP succeeds, can be quite valuable since it allows you to test ideas quicker.
I personally find it to be a big win, even though I also spend a lot of time fighting the AI. But I wouldn’t want to build on top of AI code without cleaning it up myself.
There are also some tasks I’ve learned to just do myself: eg I do not let the AI decide my data model/database schema. Data is too important to leave it up to an AI to decide. Also outside of simple CRUD operations, it generates quite inefficient database querying so if it’s on a critical path, perhaps write the queries yourself.
Tools should enable creativity and problem-solving, not become problems themselves. The best languages fade into the background, becoming almost invisible as you express your solution. When the language constantly demands center stage, something has gone fundamentally wrong with its design philosophy.
I like Nim- compiles to C so you get similarly close to the instructions and you can use a lot of high level features if you want to, but you can also stay close to the metal.
"and all was bounds-checked at memory cost but the results were awesome. Defensive programming all the way : all bugs are reduced to zero right from the start."
a bit LOL, isn't it?
also the part about terraform, ansible and the other stuff.
In the abstract, the simplicity of C has a definite appeal. However, pragmatically, the sense that I am mowing the lawn with a pair of scissors gets tiring quickly.
Linux/UNIX distributions are essentially C development environments, and their package managers are basically C language package managers… so, you needn’t do everything yourself, just grab the source packages.
> It was supposed to be a short mission I thought I could learn Common Lisp in ten days and hack a quick server management protocol. I ended up writing throw-away Common Lisp code that generated C for a fully-fledged ASN.1 parser and query system for a custom Common Lisp to C SNMP server.
I believe it. And I'd love to see it and hack on it, if it were open source.
This whole kc3 thing looks pretty interesting to me. I agree with the premise. It's really just another super-C that's not C++, but that's a pretty good idea for a lot of things because the C ABI is just so omnipresent.
About a year ago, I had gotten fed up with what felt like overlyb strict context requirements basically giving me to abandon years of work and thought I'd try my hand at C++ again.
I wanted to do this on Linux, because I my main laptop is a Linux machine after my children confiscated my Windows laptop to play Minecraft with the only decent GPU in the house.
And I just couldn't get past the tooling. I could not get through to anything that felt like a build setup that I'd be able to replicate in my own.
On Windows, using Visual Studio, it's not that bad. It's a little annoying compared to a .NET project, and there are a lot more settings to worry about, but at the end of the day VS makes the two but very different from each other.
I actually didn't understand that until I tried to write C++ on Linux. I thought C++ on Windows was worlds different than C#. But now I've seen the light.
I honestly don't know how people do development with on Linux. Make, Cmake, all of that stuff, is so bad.
IDK, maybe someone will come along and tell me, "oh, no, do this and you'll have no problems". I hope so. But without that, what a disgusting waste of time C and C++ is on Linux.
I find make, cmake, and the other stuff annoying also. For personal stuff I just use a build.sh file. For debugging I use gf2, which is a gdb frontend. Hopefully raddebugger gets ported to linux soon. One nice tool I like on linux for prototyping is the tiny c compiler, because it compiles 7x faster than gcc or clang. It is also much faster than the visual studio compiler. I remember trying to get the tiny c compiler to work on windows; it can compile things, but I couldn't get it to generate pdb files for debug info.
Rust evangelism is probably the worst part of Rust. Shallow comments stating Rust’s superiority read to me like somebody who wants to tell me about Jesus.
Definitely not true. One look at what a modern C compiler does to optimize the code you give it will disabuse you of that notion.
There's nothing special or magic about C code, and, if anything, C has moved further and further away from its "portable assembler" moniker over time. And compilers can emit very similar machine instructions for the same type of algorithm regardless of whether you're writing C, Rust, Go, Zig, etc.
Consider, for example, that clang/LLVM doesn't even really compile C. The C is first translated into LLVM's IR, which is then used to emit machine instructions.
It sort of is and isn't true. CPUs are designed to make certain programming models work very well, and they've done so at the costs of making other kinds of programming paradigms work well (as compared to, say, a GPU, which wants a programming model for which C and C-like languages are clearly ill-fitting). So it's not a wrong statement if you think of it as "designed for C-like languages."
But if you're using it in the sense of "C is a privileged language in terms of its connection to hardware architecture, " well, C isn't, and that statement is patently false. There's not a major difference between C, C++, Rust, Zig--even going as far afield as bytecode languages like Java and C#, or fully interpreted stuff like Python or Perl, especially as far as computer architects are concerned.
(And in the sense of "this is the language that architects care most about for tuning performance," I think that's actually C++, simply because that tends to be the language for the proprietary HPC software that pays the big bucks for compiler support.)
I don't think that's true. They've co-evolved, but C simply does less.
I've never seen a good way to make a CPU that's good for "not C" languages. Those are usually by people who are aggressively uninterested in being fast and so insist on semantics that simply wouldn't get faster if done in hardware. Like the way most Haskell programs execute is just bad and based on bad ideas.
But in both cases, modern CPUs are mostly network- then I/O- then memory-bound. Most C programs aren't written to respect that very well.
That's fair, but to me what drags C and C++ really down for me is the difficulty in building them. As I get older I just want to write the code and not mess with makefiles or CMake. I don't want starting a new project to be a "commitment" that requires me to sit down for two hours.
For me Rust isn't really competing against unchecked C. It's competing against Java and boy does the JVM suck outside of server deployments. C gets disqualified from the beginning, so what you're complaining about falls on deaf ears.
I'm personally suffering the consequences of "fast" C code every day. There are days where 30 minutes of my time are being wasted on waiting for antivirus software. Thinks that ought to take 2 seconds take 2 minutes. What's crazy is that in a world filled with C programs, you can't say with a good conscience that antivirus software is unnecessary.
> That's fair, but to me what drags C and C++ really down for me is the difficulty in building them. As I get older I just want to write the code and not mess with makefiles or CMake. I don't want starting a new project to be a "commitment" that requires me to sit down for two hours.
Also, integrating 3rd party code has always been one of the worst parts of writing a C or C++ program. This 3p library uses Autoconf/Automake, that one uses CMake, the other one just ships with a Visual Studio .sln file... I want to integrate them all into my own code base with one build system. That is going to be a few hours or days of sitting there and figuring out which .c and .h files need to be considered, where they are, what build flags and -Ddefines are needed, how the build configuration translates into the right build flags and so on.
On more modern languages, that whole drama is done with pip install or cargo install.
.PHONY: all
all:
cc -o progname -std=c99 -pedantic -Wall -Wextra -Wpedantic -O0 *.c
Or something along those lines. Move some stuff to variables (CC, CFLAGS, etc.) for release. Can use object files for larger programs (but often isn't needed, certainly not when starting out).
I do agree that the general experience of Rust is a lot better. But I also think a lot of C projects have overcomplicated build systems that aren't really needed.
Feature wise, yes. C forces you to keep a lot of irreducible complexity in your head.
> Rust has a much, much slower compiler than pretty much any language out there
True. But it doesn't matter much in my opinion. A decent PC should be able to grind any Rust project in few seconds.
> Rust applications are sometimes
Sometimes is a weasel word. C is sometimes slower than Java.
> Rust takes most people far longer to "feel" productive
C takes me more time to feel productive. I have to write code, then unit test, then property tests, then run valgrind, check ubsan is on. Make more tests. Do property testing, then fuzz testing.
Or I can write same stuff in Rust and run tests. Run miri and bigger test suite if I'm using unsafe. Maybe fuzz test.
> That is demonstrably false, unless your definition of "decent PC" is something that costs $4000.
How is it demonstrably false? I'm on 5900x and Rust compilation speed was never an issue for me.
More like $1500. $500 for 9950x. $200 for Mobo, $200 for memory and $100 for 1Tb ssd and power supply for $100. Coolers and case by desire. GPU optional.
Only way to get to $4000 in a PC is you are buying fancy components or you bought latest xx90 card.
I once moved a C++ project to C, and compile times went from 15 minutes to 5 seconds. It was a huge productivity boost. LLVM being slow to compile is also one of the reasons the Zig folk are looking to make it an optional dependency.
I remember a project that used boost for very few things, but it included a single boost header in almost every file. That one boost header absolutely inflated the build times to insane levels.
I don't feel like I need to keep a lot of complexity in my head for C. But one needs to have a concept of how to organize things. I guess Rust forces this onto you.
> C takes me more time to feel productive. I have to write code, then unit test, then property tests, then run valgrind, check ubsan is on. Make more tests. Do property testing, then fuzz testing.
Good for you. Like the grandparent commenter said, for others these tradeoffs might be important. E.g.:
> I am disappointed with how poorly Rust's build scales, even with the incremental test-utf-8 benchmark which shouldn't be affected that much by adding unrelated files. (...)
> I decided to not port the rest of quick-lint-js to Rust. But... if build times improve significantly, I will change my mind!
Look you're picking a memory unsafe language versus a safe one. Whatever meager gains you save on compilation times (and the link shows the difference is meager if you aren't on a MacOS, which I'm not) will be obliterated by losses in figuring out which UB nasal demon was accidentally released.
This is like that argument that dynamic types save time, because you can catch error in tests. But then have to write more tests to compensate, so you lose time overall.
> C takes me more time to feel productive. I have to write code, then unit test, then property tests, then run valgrind, check ubsan is on. Make more tests. Do property testing, then fuzz testing.
Have you tried looking around and noticing nobody else does that and it's, like, fine?
* Rust is vastly easier to get started with as a new programmer than C or C++. The quality and availability of documentation, tutorials, tooling, ease of installation, ease of dependency management, ease of writing tests, etc. Learning C basically requires learning make / cmake / meson on top of the language, and maybe Valgrind and the various sanitizers too. C's "simplicity" is not always helpful to someone getting started.
* The Rust compiler isn't particularly slow. LLVM is slow. Monomorphization hurts the language, but any other language that made the same tradeoff would see the same problems. The compiler has also gotten much much faster in the last few years and switching linkers or compiler backends makes a huge difference.
* Orgs that have studied tracked this don't find Rust to be less productive. Within a couple of months programmers tend to be just as if not more productive than they were previously with other languages. The ramp-up is probably slower than, say, Go, but it's not Scala / Haskell. And again, the tooling & built in test framework really helps with productivity.
* Rust applications are very rarely slower than comparable C applications
* Rust applications do tend to be larger than comparable C applications, but largely because of static vs. dynamic linking and larger debuginfo.
Neither of our opinions make someone else's opinion false.
- Rust may have felt easier for you or some, but certainly not everyone or even most. It might be worth it, but it's not an easy on ramp for many.
- Excuses for slow compile times don't make compile times faster.
- That's why I said "feel." There are warm fuzzy and cold prickly human things in here. Studies that pretend at measuring something we all know cannot be measured are summarily dismissed.
- More excuses do not make a statement false. Rust compile times are some of the slowest I have seen in >25 years of development.
Again, the trade-offs work for many people and orgs. That's great!
That doesn't make them disappear or become, "false."
It's precisely this tone and attitude (that is so prevalent in the community) that keeps so many of us away.
The best feature of C is the inconvenience of managing dependencies. This encourages a healthy mistrust of third-party code. Rust is unfortunately bundled with an excellent package manager, so it's already well on its way to NPM-style dependency hell.
Can't help but agree, as much as I prefer Rust over C.
On the other hand, C definitely goes too far in to the opposite extreme. I am very tired of reinventing wheels in C because integrating third-party dependencies is even more annoying than writing and maintaining my own versions of common routines.
(And yes, I was considering if I should shout in capslock ;) )
I have seen so many fresh starts in Rust that went great during week 1 and 2 and then they collided with the lifetime annotations and then things very quickly got very messy. Let's store a texture pointer created from an OpenGL context based on in-memory data into a HashMap...
impl<'tex,'gl,'data,'key> GlyphCache<'a> {
Yay? And then your hashmap .or_insert_with fails due to lifetime checks so you need a match on the hashmap entry and now you're doing the key search twice and performance is significantly worse than in C.
Or you need to add a library. In C that's #include and a -l linker flag. In Rust, you now need to work through this:
> Or you need to add a library. In C that's #include and a -l linker flag. In Rust, you now need to work through [link to cargo docs]
This is just bizarre to me, the claim that dependency management is easier in C projects than in Rust. It is incredibly rare that adding a dependency to a C project is just an #include and -l flag away. What decent-sized project doesn't use autotools or cmake or meson or whatever? Adding a dependency to any of those build systems is more work than adding a single, short line to Cargo.toml.
And even if you are just using a hand-crafted makefile (no thank you, for any kind of non-trivial, cross-platform project), how do you know that dependency is present on the system? You're basically just ignoring that problem and forcing your users to deal with it.
Long compile times with Rust don't really bother me that much. If it's someone else's program that I just want to build and run for myself, the one-time hit of building it isn't a big deal. I can be patient.
If it's something I'm actively developing, the compile is incremental, so it doesn't take that long.
What does often take longer than I'd like is linking. I need to look into those tricks where you build all the infrequently-changing bits (like third-party dependent crates) into a shared library, and then linking is very quick. For debug builds, this could speed up my development cycle quite a bit.
The C standard makes provisions for compiler implementers which absolve them from responsibility of ignoring the complexity of the C language. Since most people never actually learn all the undefined behavior specified in the standard and compilers allow it, it might seem the language is simpler, but it's actually only compilers which are simpler.
You can argue that Rust generics are a trivial example of increased complexity vs the C language and I'd kinda agree: except the language would be cumbersome to use without them but with all the undefined C behavior defined. Complexity can't disappear, it can be moved around.
It makes the C semantics you are coding against more complex. Lots of unlisted or handwaved things in the spec become problems you need to keep in mind far more often than you would with better definitions.
It is when the root cause is tooling, not language features.
You don't need to wait for long compile times in Haskell if you don't want to, there are interpreters and REPLs available as well.
You don't need to wait for long compile times in C++ if you don't want to, most folks use binary libraries, not every project is compiled from scratch, there are incremental compilers and linkers, REPLs like ROOT, managed versions with JIT like C++/CLI, and if using modern tooling like Visual C++ or Live++, hot code reloading.
IMO these are the major downsides of Rust in descending order of importance:
- Project leadership being at the whims of the moderators
- Language complexity
- Openly embracing 3rd party libraries and ecosystems for pretty much anything
- Having to rely on esoteric design choices to wrestle the compiler into using specific optimizations
- The community embracing absurd design complexity like implementing features via extension traits in code sections separated from both where the feature is going to be used and where either the structs and traits are implemented
- A community of zealots
I think the upsides easily outcompete the downsides, but I'd really wish it'd resolve some of these issues...
At least apparent complexity. See "Expert C Programming: Deep C Secrets" which creeps up on you shockingly fast because C pretends to be simple by leaving things to be undefined but in the real life things need some kind of behavior.
Rust makes explicit what the C standard says you can't ignore but it's up to you and not the compiler. Rust is a simpler and easier language than C in this sense.
I like the Rust ADTs and the borrow checker, but I can't stand the syntax. I just wish it had Lisp syntax, but making it myself is far beyond my abilities.
That really depends what you want to do. All that security in Rust is only needed if there is a danger of hacks compromising the system.
The moment you start building something that's not exposed to the internet and hacking it has no implications, C beats it due to simplicity and speed of development .
It also depends on what you want to get away from.
I don't disagree that Rust might technically be a better option for a new project, but it's still a fairly fast moving language with an ecosystem that hasn't completely settled down. Many are increasingly turned off by the fast changing developer environments and ecosystems, and C provides you with a language and libraries that has already been around for decades and aren't likely to change much.
There are also so many programming concepts and ideas in Rust, which are all fine and useful in their own right, but they are a distraction if you don't need them. Some might say that you could just not use them, but they sneak up on you in third party libraries, code snippets, examples and suggestions from others.
Personally I find C a more cosy language, which is great for just enjoying programming for a bit.
C might beat Rust at simplicity and speed of development (don't know, I never developed in Rust) but I remember why I stopped developing in C about 30 years ago: the hundreds of inevitably bug ridden lines of C to build a CGI back then (malloc, free, strcpy, etc) vs little more than string slicing and "string" . "concatenation" in Perl and forget about everything else. That could have been Python (which I didn't know about,) or the languages there were born in those years: Ruby and PHP. Even Java was simpler to write. Runtime speed was seldom a problem even in the 90s. C programs are fast to run but they are not fast to develop.
> All that security in Rust is only needed if there is a danger of hacks compromising the system.
It's not just about security, it's about reliability too. If my program crashes because of a use-after-free or null pointer dereference, I'm going to be pissed off even if there aren't security implications.
I prefer Rust to C for all sorts of projects, even those that will never sit in front of a network.
Correctness is not just about security. And the threat environment to which a program may eventually be exposed is not always obvious up front.
Also, no: that's only true for some kinds of programs. Rust, c++, and go all have a much easier ecosystem for things like data structures and more complex libraries that make writing many programs much easier than in C.
The only place I find C still useful over one of the other three is embedded, mostly because of the ecosystem, and rust is catching up there also.
(This is somewhat ironic, because I teach a class in C. It remains a useful language when you want someone to quickly see the relationship between the line of code they wrote and the resulting assembly, but it's also fraught - undefined behavior lurks in many places and adds a lot of pain. I will one day switch the class to rust, but I inherited the C version and it takes a while.)
> much easier ecosystem for things like data structures and more complex libraries that make writing many programs much easier than in C.
So many people have implemented those data structures though, and they are available freely and openly, you can choose to your liking, i.e. ohash, or uthash, or khash, etc. and that is only for a hash table.
Those complex libraries are out there, too, for C, obviously.
The reason for why it is not in the standard library is obvious enough: there are many ways to implement those data structures, and there is no one size that fits all.
There are! But composability is easier in the languages that have generics/templates/etc. There's less passing around of function pointers and writing of custom comparator functions, using something like binary search or sort as an example, and the fact that those comparators can be inlined can often make the rust or C++ version faster than the "as simple to write" C version.
Obviously, all of these languages are capable of doing anything the others can. Turing complete is turing complete. But compare the experience of writing a multithreaded program that has, as part of it, an embedded HTTP server that provides statistics as it runs. It's painful in C, fairly annoying in C++ unless you match well to some existing framework, pretty straightforward in Rust, and kinda trivial in Go.
One comment talked about not using a (faster) B-Tree instead of a AVL-tree in C, because of the complexity (thus maintenance burden and risk of mistakes) it would add to the code.
Not really. Rustup only ships a limited number of toolchains, with some misses that (for me) are real head-scratchers. i686-unknown-none, for example. Can't get it from rustup. I'm sure there's a way to roll your own toolchain, but Rust's docs might as well tell you to piss up a rope for how much they talk about that.
Why is this important? C is the lingua franca of digital infrastructure. Whether that's due to merit or inertia is left as an exercise for the reader. I sure hope your new project isn't meant to supplant that legacy infrastructure, 'cause if it needs to run on legacy hardware, Rust won't work.
This is an incredibly annoying constraint when you're starting a new project, and Rust won't let you because you can't target the platform you need to target. For example, I spent hours building a Rust async runtime for Zephyr, only to discover it can't run on half the platforms Zephyr supports because Rust doesn't ship support for those platforms.
Are what cargo, rustc, etc. are expected to run on. You probably meant target.
> i686-unknown-none
Is admittedly a missing target. `x86_64-unknown-none` specifies stuff like `extern "C"`'s ABI (per https://doc.rust-lang.org/rustc/platform-support/x86_64-unkn... ) which is a lot less universal/appropriate for i686, where AFAIK everyone chooses their own different incompatible ABIs - which might be the reason it's not provided? Usually you want to pick an i686-unknown-* target that aligns more closely with your own needs (e.g. your desired object/library/binary file format, abi, bootloader, ...?)
> C is the lingua franca of digital infrastructure
Is it, though? It feels more like how the French saw the French language as "the" language of the world, by basically discounting as unimportant everywhere that didn't use French.
Ok, no, yeah, I see it now. The Lingua Franca is right
> Defensive programming all the way : all bugs are reduced to zero right from the start
Has it been fuzzed? Have you had someone who is very good at finding bugs in C code look at it carefully? It is understandable if the answer to one or both is "no". But we should be careful about the claims we make about code.
I'm kinda in the opposite camp. After doing a bunch of VB in my tweens and teens, I learned Java, C, and C++ in college, settling on mostly C for personal and professional projects. I became a core developer of Xfce and worked on that for 5 years.
Then I moved into backend development, where I was doing all Java, Scala, and Python. It was... dare I say... easy! Sure, these kinds of languages bring with them other problems, but I loved batteries-included standard libraries, build systems that could automatically fetch dependencies -- and oh my, such huge communities with open-source libraries for nearly anything I could imagine needing. Even if most of the build systems (maven, sbt, gradle, pip, etc.) have lots of rough edges, at least they exist.
Fast forward 12 years, and I find myself getting back in to Xfce. Ugh. C is such a pain in the ass. I keep reinventing wheels, because even if there's a third-party library, most of the time it's not packaged on many of the distros/OSes our users use. Memory leaks, NULL pointer dereferences, use-after-free, data races, terrible concurrency primitives, no tuples, no generics, primitive type system... I hate it.
I've been using Rust for other projects, and despite it being an objectively more difficult language to learn and use, I'm still much more productive in Rust than in C.
I think Rust is harder to learn, but once you grok it, I don't think it's harder to use, or at least to use correctly. It's hard to write correct C because the standard tooling doesn't give you much help beyond `-Wall`. Rust's normal error messages are delightfully helpful. For example, I just wrote some bad code and got:
I even had to cheat a little to get that far, because my editor used rust-analyzer to flag the error before I had the chance to build the code.Also, I highly recommend getting into the habit of running `cargo clippy` regularly. It's a wonderful tool for catching non-idiomatic code. I learned a lot from its suggestions on how I could improve my work.
> I think Rust is harder to learn, but once you grok it, I don't think it's harder to use, or at least to use correctly. It's hard to write correct C because the standard tooling doesn't give you much help beyond `-Wall`.
When I say Rust is harder to use (even after learning it decently well), what I mean is that it's still easier to write a pile of C code and get it to compile than it is to write a pile of Rust code and get it to compile.
The important difference is that the easier-written C code will have a bunch of bugs in it than the Rust code will. I think that's what I mean when I say Rust is harder to use, but I'm more productive in it: I have to do so much less debugging when writing Rust, and writing and debugging C code is more difficult and takes up more time than writing the Rust code (and doing whatever less debugging is necessary there).
> Also, I highly recommend getting into the habit of running `cargo clippy` regularly. It's a wonderful tool for catching non-idiomatic code.
That's a great tip, and I usually forget to do so. On a couple of my personal projects, I have a CI step that fails the build if there are any clippy messages, but I don't use it for most of my personal projects. I do have a `cargo fmt --check` in my pre-commit hooks, but I should add clippy to that as well.
If you're using VS Code then you can add `"rust-analyzer.check.command": "clippy"` to your `settings.json`. I assume there's a similar setting for rust-analyzer in other editors.
Neovim:
You might want to reconsider use of rust-analyzer, it isn't safe to use on code you haven't written yourself.
https://rust-analyzer.github.io/book/security.html
> it isn't safe to use on code you haven't written yourself
Neither is cargo (nor npm, nor any other package manager, for that matter).
I'm not sure what value being that paranoid is buying you in the long run.
That's a fair distinction. Basically, it's easier to write C that compiles than Rust that compiles, but it's harder to write correct C than correct Rust.
Regarding Clippy, you can also crank it up with `cargo clippy -- -Wclippy::pedantic`. Some of the advice at that level gets a little suspect. Don't just blindly follow it. It offers some nice suggestions though, like:
that you don't get by default.The truly pedantic setting here would by complaining about the magic number.
Why 1_000_000_000_000, what does that number mean.
It is for free to:
since the compiler will just inline it.The readability problem was never the lack of separators, since that number might be the wrong number regardless.
You can also add #![warn(clippy::all, clippy::pedantic)] to your main.rs/lib.rs file to get those lints project-wide.
I’d add that the Rust code and C code will probably have the same number of bugs. The C code will likely have some vulnerabilities on top of those.
Rust doesn’t magically make the vast majority of bugs go away. Most of bugs are entirely portable!
Rust allows to provide more information about types (generic types, pointer usage) and checks it, while in C you have to rely on doc comments and checking the code manually. Or am I wrong and C allows to specify pointer nullability, pointer ownership and array bounds?
None of those things feature in any problem I deal with on a daily basis, whatever language I use.
So for example today I dealt with a synchronization issue. This turned out to not be a code bug but a human misunderstanding of a protocol specification saga, which was not possible to code into a type system of any sort. The day before was a constraint network specification error. In both cases the code was entirely irrelevant to the problem.
Literally all I deal with are human problems.
My point is Rust doesn't help with these at all, however clever you get. It is no different to C, but C will give you a superset of vulnerabilities on top of that.
Fundamentally Rust solves no problems I have. Because the problems that matter are human ones. We are too obsessed with the microscopic problems of programming languages and type systems and not concentrating on making quality software which is far more than just "Rust makes all my problems go away" because it doesn't. It kills a small class of problems which aren't relevant to a lot of domains.
(incidentally the problems above are implemented in a subset of c++)
> So for example today I dealt with a synchronization issue. This turned out to not be a code bug but a human misunderstanding of a protocol specification saga, which was not possible to code into a type system of any sort.
Maybe not in a reasonable language no, but there are advances in type systems that are making ever larger classes of behaviours encodable into types. For example, algebraic effects (can this function throw, call a remote service etc)
https://koka-lang.github.io/koka/doc/index.html
linear types (this method must be called only once etc)
https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/line...
dependent typing (f(1) returning a different type from f(2), verifiable at compile time),
https://fstar-lang.org/index.html
Some of these features will eventually make it to “normal” PL. For example, Scala now has dependent types,
https://dotty.epfl.ch/docs/reference/new-types/match-types.h...
and Java can support linear type checking,
https://checkerframework.org/manual/#must-call-checker
> None of those things feature in any problem I deal with on a daily basis, whatever language I use.'
I run into those things nearly daily, so... ok then.
Do not feel bad friend, It's not you.. there is definitely inconsistencies here. Maybe they work on absolutely pristine perfect codebases.
Vulnerabilities are bugs, so the C code will have more bugs than the Rust program.
You might say that the C and Rust code will have the same number of logic errors, but I'm not convinced that's the case either. Sure, if you just directly translate the C to Rust, maybe. But if you rewrite the C program in Rust while making good use of Rust's type system, it's likely you'll have fewer logic errors in the Rust code as well.
Rust has other nice features that will help avoid bugs you might write in a C program, like most Result-returning functions in the stdlib being marked #[must_use], or match expressions being exhaustive, to name a couple things.
> most Result-returning functions in the stdlib being marked #[must_use]
Actually it's a bit cleverer than that, and some people might benefit from knowing this. The Result type itself is marked #[must_use]. If you're writing a Goat library and you are confident that just discarding a Goat is almost always a mistake regardless of the context in which they got a Goat you too should mark your Goat type #[must_use = "this `Goat` should be handled properly according to the Holy Laws of the Amazing Goat God"] and now everybody is required to do that or explicitly opt out even for their own Goat code.
Obviously don't do this for types which you can imagine reasonable people might actually discard, only the ones where every discard is a weird special case.
Types I like in Rust which help you avoid writing errors the compiler itself couldn't possibly catch:
Duration - wait are these timeouts in seconds or milliseconds? It's different on Windows? What does zero mean, forever or instant ?
std::cmp::Ordering - this Doodad is Less than the other one
OwnedFd - it's "just" a file descriptor, in C this would be an integer, except, this is always a file descriptor, it can't be "oops, we didn't open a file" or the count of lines, or anything else, we can't Add these together because that's nonsense, they're not really integers at all.
I'd say whether Rust helps you reduce bugs depends on how good you are at creating abstractions and/or encoding properties in the type system.
Most bugs are way above that level of abstraction and thought.
[Citation needed].
This is objectively nonsense
Interesting use of the word 'objectively' there.
How can it not be true? One of the primary features of the rust compiler is enforcing memory safety at compile-time. C doesn't have anything like that. There are an entire class of bugs that are impossible to implement in rust.
> it's still easier to write a pile of C code and get it to compile than it is to write a pile of Rust code and get it to compile.
As someone who is more familiar with Rust than C: only if you grok the C build system(s). For me, getting C to build at all (esp. if I want to split it up into multiple files or use any kind of external library) is much more difficult than doing the same in Rust.
> Also, I highly recommend getting into the habit of running `cargo clippy` regularly.
You can also have that hooked up to the editor, just like `cargo check` errors. I find this to be quite useful, because i hace a hard time getting into habits, especially for thing that i'm not forced to do in some way. It's important that those Clippy lints are shown as soft warnings instead of hard errors though, as otherwise they'd be too distracting at times.
Agree, Rust is quite hard to learn, but now that I know it I have a hard time writing anything else. It really gives you the best of a lot of worlds.
Granted I can still crank out a python program faster, that kinda works but god forbid you need to scale it or use any sort of concurrency at all.
* Rust errors can be equally unhelpful. Also, the error you posted is hands down awful. It doesn't tell you what went wrong, and it's excessively naive to rely on compiler to offer a correct fix in all but the most trivial cases. When errors happen, it's a consequence of an impasse, a logical contradiction: two mutually exclusive arguments have been made: a file was assumed to exist, but was also assumed not to exist -- this is what's at the core of the error. The idiotic error that Rust compiler gave you doesn't say what were the assumptions, it just, essentially, tells you "here's the error, deal with it".
* In Rust, you will have to deal with a lot of unnecessary errors. The language is designed to make its users create a host of auxiliary entities: results, options, futures, tasks and so on. Instead of dealing with the "interesting" domain objects, the user of the language is mired in the "intricate interplay" between objects she doesn't care about. This is, in general, a woe of languages with extensive type systems, but in Rust it's a woe on a whole new level. Every program becomes a Sisyphean struggle to wrangle through all those unnecessary objects to finally get to write the actual code. Interestingly though, there's a tendency in a lot of programmers to like solving these useless problems instead of dealing with the objectives of their program (often because those objectives are boring or because programmers don't understand them, or because they have no influence over them).
I don't follow your first point—the compiler is pointing out exactly what the problem is (the argument has the incorrect type) and then telling you what you likely wanted to do (borrow the String). What would you see as a more helpful error message in this case?
The compiler says "expected X, but found Y". I don't know how to interpret this: is the type of the thing underlined with "^^^" X or Y? "Expected" and "found" are just like "up" and "down" in space: they are meaningless if you don't know what the compiler expects (and why should it?).
What it needs to say is something along the lines of "a function f is defined with type X, but is given an argument of type Y": maybe the function should be defined differently, maybe the argument needs to change -- it's up to the programmer to decide.
I dunno, I feel like if you've used a compiler regularly, "expected X, but found Y" is a pretty common idiom/shorthand that people understand. Your wordier version of that feels unnecessary to me.
I don't see any way that the use if expected and found can be ambiguous for a type conflict.
I buy a fruit mixer from Amazon.com ; I send it back along with a note: expected a 230VAC mixer, found a 110VAC mixer.
C is a low level language and deals with things close to the metal. It's probably not fun to write a large business app in barebones C but you having control over low level things makes other things possible and very fast too. Depending on the type of problem you have use the appropriate and favorite language.
Since it's underlining code you wrote, it must be "found" that is highlighted, not "expected". Much like up and down, gravity exists to ground all of us in the same direction.
I'm over here with TTS: Underlining in a terminal rarely translates to audio. It isn't the only consideration that needs to be made, when making things clear.
except nobody has to cater to every worst case scenario
So every disabled programmer is now a "worst case scenario"?
The biggest problem with C is that doesn't even have enough features to help you build the features and abstractions you need and want.
For example with C++ the language offers enough functionality that you can create abstractions at any level, from low level bit manipulation to high level features such as automatic memory management, high level data objects etc.
With C you can never escape the low level details. Cursed to crawl.
Just FYI.
Back in 1994/95, I wrote an API, in C, that was a communication interface. We had to use C, because it was the only language that had binary/link compatibility between compilers (the ones that we used).
We designed what I call "false object pattern." It used a C struct to simulate a dynamic object, complete with function pointers (that could be replaced, in implementation), and that gave us a "sorta/kinda" vtable.
Worked a charm. They were still using it, 25 years later.
That said, I don't really miss working at that level. I have been writing almost exclusively in Swift, since 2014.
> We designed what I call "false object pattern." It used a C struct to simulate a dynamic object, complete with function pointers (that could be replaced, in implementation), and that gave us a "sorta/kinda" vtable.
You were not alone in this. It is the basis of glib's GObject which is at the bottom of the stack for all of GTK and GNOME.
sadly glib doesn't have error handling for memory errors,it will just crash. otherwise it can be widely used as a oop c
Sure, that's a pretty common pattern in use in C to this day. It's a useful pattern, but it's still all manual. Forget to fill in a function pointer in a struct? Crash. At least with C++ it will fail to compile if you don't implement a method that you have to implement.
At least in C it's only plain old function pointers.
You don't have to think about exceptions, overloaded operators, copy constructors, move semantics etc.
You still need to think about error handling, and it's not standardized because everyone else will also have to think about it ad hoc.
You'll also still need to think about when to copy and move ownership, only without a type system to help you tell which is which, and good luck ensuring resources are disposed correctly (and only once) when you can't even represent scoped objects. `goto` is still the best way to deal with destructors, and it still takes a lot of boilerplate.
But you have a choice, you don't have to implement all of C++/Rust.
The beauty of C is that it allows you to pick your level of complexity.
Of course you can. It's quite the opposite actually. The downside is that in C you have to code a bunch of abstractions _yourself_. See how large projects like the Linux kernel make extensive use of macros to implement an object system.
Thank you for your work on XFCE. It's been the best WM/UI for me for over a decade.
Have you looked at Zig? It is often termed a modern C where Rust is the modern C++. Seems like a good fit.
i never really understand why these get compared. i wouldn't expect that much overlap in the audiences
zig seems like someone wanted something between C and "the good parts" of C++, with the generations of cruft scrubbed out
rust seems like someone wanted a haskell-flavoured replacement for C++, and memory-safety
i would expect "zig for C++" to look more like D or Carbon than rust. and i'd expect "rust for C" to have memory safety and regions, and probably steal a few ocaml features
The single best feature (and I would say the _core_ feature separating it from C) that C++ has to offer is RAII and zig does not have that. So I don’t know which good parts of C++ they kept. Zig is more of its own thing, and they take from wherever they like, not just C++.
> So I don’t know which good parts of C++ they kept.
comptime is a better version of C++ templates.
I really wish that were true but it isn’t. Modern C++ templates/constexpr are much more powerful and expressive than any Zig comptime equivalent.
The power and expressiveness of the C++ compile-time capabilities are the one thing I strongly miss when using other languages. The amount of safety and conciseness those features enable makes not having them feel like a giant step backward. Honestly, if another systems language had something of similar capability I’d consider switching.
I have written a lot of Zig comptime code and ended up finding the opposite. In C++ I find I have to bend over backward to get what I want done, often resulting in insane compile times. I've used metaprogramming libraries like Boost Hana before to have some more ergonomics, but even that I would consider inferior to comptime.
Out of curiosity, do you happen to have any examples of what you describe, where C++ is more powerful and expressive than Zig?
If it looks anything like what I read in "Modern C++ Design" 20+ years ago then I'll pass. That book made me realize the language wasn't for me anymore.
It looks nothing like C++ decades ago, it is effectively a completely different language. I found C++ unusable before C++11, and even C++11 feels archaic these days. Idiomatic C++20 and later is almost a decent language.
Zig has defer, which is arguably way simpler. Is there something RAII can do that defer can't?
Not everyone likes RAII by itself. Allocating and deallocating things one at a time is not always efficient. That is not the only way to use RAII but it's the most prevalent way.
defer can't emulate the destructors of objects that outlives their lexical scope. Return values and heap objects are examples of these since they outlive the function they were created in. defer only supports enqueuing actions at lexical boundaries.
If you destroy an object that outlives the lexical scope it was created in, then you have to clean up manually.
Defer can be omitted
I would say OCaml more than Haskell, but yes.
I have, and I do find Zig impressive, but it doesn't go far enough for me. I don't want a "better C", I want a better systems language that can also scale up for other uses.
I like strong, featureful type systems and functional programming; Zig doesn't really fit the bill for me there. Rust is missing a few things I want (like higher-kinded types; GATs don't go far enough for me), but it's incredible how they've managed to build so many zero- and low-cost abstractions and make Rust feel like quite a high-level language sometimes.
Rust is not a modern C++, their core models are pretty different. Both Rust and C++ can do things the other can’t do. C++ is a more focused on low-level hyper-optimized systems programming, Rust is a bit higher level and has stronger guardrails but with performance closer to a classic systems language.
I do think Zig is a worthy successor to C and isn’t trying to be C++. I programmed in C for a long time and Zig has a long list of sensible features I wish C had back then. If C had been like Zig I might never have left C.
What do you consider the difference in their core models.
Rust and C++ both use RAII, both have a strong emphasis on type safety, Rust just takes that to the extreme.
I would like to even hope both believe in 0 cost abstractions, which contrary to popular belief isn't no cost, but no cost over doing the same thing yourself.
In many cases it's not even 0 cost, it's negative cost since using declarative programming can allow the compiler to optimise in ways you don't know about.
I Really want to use Rust. But Rust has so many std functions that kind of abstracting how does it work under the hood
For Ex: I still has no idea what clone() does, how does it interact with memory, on heap or stack , does it create a new instance, or just modify metadata of that object. Sometime creating a new instance is a big no-no because it takes a lot of memory.
Same thing with "ownership transfer", is variable freed at that moment, etc.
I bet i could find answers on internet, but rust has like 500 std functions, so the task is tedious
What is your current language of choice, both C and C++ have the same problems as what you just described.
Regarding ownership transfer it is even worse in C, what if you forget, after moving an object out of a variable, to set that variable to NULL, then free that variable, that's a use after free. At least in C++ you have move semantics although it is still error prone. In rust it's a compiler error.
Copy and Clone is the same, and both are opt-in for your own types, by default the only options are move or reference for your own types, in C and C++ by default it is copy, which again leads to use after free in the situations you complained about.
I feel if these are your complaints you will actually benefit from spending some time in Rust.
If your preferred language is higher level languages with GC that is reference by default I encourage you to try any of the systems level programming languages, the things you complain about are things that are important for a language to have constructs for, reference semabtics by default causes many issues, and becomes untenable in the parallel world we live in.
Well, i dont use C++ much(i'm FW engineer, most of my stuff is in C).
The std in C is simple and explicit. For Ex: I can make an educated guess how memcpy() work by looking at its signature. It takes pointer to src and destination, and size, so i can guess it does not allocate any new memory(or if it has, it has to be some kind of optimization reason).
Another example is strstr(), it returns pointer to a piece of memory i provided to it, so i can safely do some pointer math with the return value.
It's true that i do not spend much time in Rust, so maybe i'm missing some fundamental things. I guess my mistake is trying to apply my knowledge in C to rust.
But still, it's kind of irritating now knowing (or guessing) how does function work just by looking at at its signature.
Isn't guessing what a function does based purely on its name pretty risky? There's usually nuance, so if I'm not already familiar with a function, I'm looking up its docs at least once.
Similarly, I went from writing a lot of C to Python, and I appreciate both of them for almost opposite reasons. I ended up finding I like Cython quite a bit, even though the syntax leaves much to be desired. The power, performance, and flexibility of C combined with the batteries included nature of Python is a match made in heaven.
You're also still very much free to write either language purely, and "glue" them together easily using Cython.
I will say the more recent additions to C++ at least have solved many of my long standing issues with that C-variant. Most of it was stuff that was long overdue. Like string formatting or a thread safe println. But even some of the stuff I didn’t think I would love has been amazing. Modules. Modules bro. Game changer. I’m all in. Honestly C++ is my go to for anything that isn’t just throw away again. Python will always be king of the single use scripts.
The problem is that they are _additions_, C++ has such absurd sprawl. The interactions between everything in this massive sprawl is quite difficult to grasp
That's also a problem in C land, of course, perhaps with less total sprawl.
Yeah, it has new features, but you're stuck working on a C89 codebase, good luck!
I don't know a great answer to that. I almost feel like languages should cut and run at some point and become a new thing.
Perhaps less? More like certainly a ton less. Regards, someone who uses C++ and doesn't even hate it.
I lost interest in keeping up with C++'s advances more than a decade ago.
The problem is that I want a language where things are safe by default. Many of the newer stuff added in C++ makes things safe, perhaps even to the level of Rust's guarantees -- but that's only if you use only these new things, and never -- even by accident -- use any of the older patterns.
I'd rather just learn a language without all that baggage.
> Memory leaks, NULL pointer dereferences, use-after-free
I suffered writing those for many years. I finally simply learned not to do them anymore. Sort of like there's a grain of sand on the bottom of my foot and the skin just sort of entombed it in a callous.
I've seen you make these kinds of comments before on other articles. Please stop. Not everyone is perfect and can forevermore avoid making any mistakes. I strongly suspect your opinion of your skill here is overinflated. Even if it isn't, and you really are that good, everyone cannot be in the top 0.00001% of all programmers out there, so your suggestion to "simply" learn not to make mistakes is useless.
This all just comes off incredibly arrogant, if I'm being honest.
I think a more charitable interpretation of what he said was, "after sufficient practice, I became good enough to start avoiding those pitfalls."
It's not all that different from learning any challenging task. I can't skateboard to save my life, but the fact that people can do it well is both admirable and the result of hundreds or thousands of hours of practice.
Skilled people can sometimes forget how long it took to learn their talent, and can occasionally come off as though the act is easy as a result. Don't take it too harshly.
He's not making a comment about everyone, it's a specific comment about how often long time C programmers make basic mistakes after a million SLOC or so.
In this instance Walter is correct - the mistakes he listed are very rarely made by experienced C programmers, just as ballet dancers rarely trip over their own feet walking down a pavement.
The problem of those errors being commonplace in those that are barely five years in to C coding and still have another five to go before hitting the ten year mark still exists, of course.
But it's a fair point that given enough practice and pain those mistakes go away.
> just as ballet dancers rarely trip over their own feet walking down a pavement
What about about walking down a busy construction site? The most charitable and correct interpretation I can think of is "I'm a professional. Seatbelts and OSHA destroy my productivity."
> What about about walking down a busy construction site?
Coordinated people with some years of experience pay attention to the ground and overhead cranes and conveyor belts and survive walking through construction sites, mine sites, aviation hangers, cattle yards, musters, et al on a routine basis. I'm 60+ and have somehow navigated all those environs - including C for critical system control.
These are dangerous environments. No one denies this. It's still true that the longer you inhabit such spaces the safer your innate learned behaviour is.
C has seatbelts and OSHA - valgrind, et al tools abound for sanity checking.
Walter's GP statement is literally little more than "eventually you grow out of making the simple basic maistakes" - eventually, after some years of practice - which is a real problem with C, it takes time to not make the basic mistakes. After all that, there's always room, in C, in Rust, whatever, to make non basic non obvious mistakes.
> After all that, there's always room, in C, in Rust, whatever, to make non basic non obvious mistakes.
Correct, I guess. The number of relatively obvious mistakes should decrease with experience. And it stands to reason that eventually it settles near zero for some part of developer community.
How close to zero and which part of community? Statistic is scarce.
> C has seatbelts and OSHA - valgrind, et al tools abound for sanity checking.
Optional tools with no general enforcement. That is more like elective vaccination or travel advisories. That is, no, no seatbelts and no OSHA.
I'd imagine the main way one reduces instances of these mistakes is to restrict resource ownership into certain patterns which have a clear place for freeing, and rules that ensure it's always reached, and only once.
There are many approaches depending on the type of program or suite of programs being built.
Always pairing the creation of free() code and functions with every malloc() is one discipline.
Another, for a class of C utilities, is to never free() at all .. "compute anticipated resource limits early, malloc and open pipes in advance, process data stream and exit when done" works for a body of cases.
In large C projects of times past it's often the case that resource management, string handling, etc are isolated and handled in dedicated sub sections that resemble the kinds of safe handling methods baked into modern 'safe' languges.
You come off as incredibly arrogant too, you just don't realise it because you have the current mainstream opinion and the safety of a crowd.
Do you know how fucking obnoxious it is when 200 people like you come into every thread to tell 10 C or Javascript developers that they can't be trusted with the languages and environments they've been using for decades? There are MILLIONS of successful projects across those two languages, far more than Rust or Typescript. Get a fucking grip.
Nobody is telling JS developers that Rust will save them, chill.
Surely it is better than yet another self-promoting mention of his programming language on every unrelated C, Rust or Zig post?
[dead]
That is very interesting. You have quite the resume too. While I've dabbled in nearly everything, I'm a day to day pro C# developer and I absolutely love it. I've never been upset or had a complaint. If I were forced off for some reason, I'd just go to Typescript. I can't imagine using C. Perhaps with some form of AI valgrind. The problems C solved are just not relevant any longer, and it remains entrenched in 2025. Rust with AI analysis will be amazing to see the results of.
It looks to me like C is still very relevant, and isn't going anywhere anytime soon.
I realize a lot of people don't want to use it; and that's fine, don't use it.
It depends on problem domain. If you're writing kernel level code or device drivers you wouldn't use C# or typescript.
There are at least a few kernels, bootloaders, and device drivers written in C# out there… granted for hobby/research.
https://github.com/Michael-K-GH/RoseOS
https://vollragm.github.io/posts/kernelsharp/
https://www.gocosmos.org
https://www.microsoft.com/en-us/research/project/singularity... (asm, C, C++, largely C# (Sing#))
[dead]
I fully understand that sentiment. For several years now, I have also felt the strong urge to develop something in pure C. My main language is C++, but I have noticed over and over again that I really enjoy using the old C libraries - the interfaces are just so simple and basic, there is no fluff. When I develop methods in pure C, I always enjoy that I can concentrate 100% on algorithmic aspects instead of architectural decisions which I only have to decide on because of the complexity of the language (C++, Rust). To me, C is so attractive because it is so powerful, yet so simple that you can hold all the language features in your head without difficulty.
I also like that C forces me to do stuff myself. It doesn't hide the magic and complexity. Also, my typical experience is that if you have to write your standard data structures on your own, you not only learn much more, but you also quickly see possibly performance improvements for your specific use case, that would have otherwise been hidden below several layers of library abstractions.
This has put me in a strange situation: everyone around me is always trying to use the latest feature of the newest C++ version, while I increasingly try to get rid of C++ features. A typical example I have encountered several times now is people using elaborate setups with std::string_view to avoid string copying, while exactly the same functionality could've been achieved by fewer code, using just a simple raw const char* pointer.
About 16 years ago I started working with a tech company that used "C++ as C", meaning they used a C++ compiler but wrote pretty much everything in C, with the exception of using classes, but more like Python data classes, with no polymorphism or inheritance, only composition. Their classes were not to hide, but to encapsulate. Over time, some C++ features were allowed, like lambdas, but in general we wrote data classed C - and it screamed, it was so fast. We did all our own memory management, yes, using C style mallocs, and the knowledge of what all the memory was doing significantly aided our optimizations, as we targeted to be running with on cache data and code as much as possible. The results were market leading, and the company's facial recognition continually lands in the top 5 algorithms at the annual NIST FR Vendor test.
Funnily enough, 16 years ago, I too was in exactly this type of company. C++, using classes, inheritance only for receiving callbacks, most attributes were public (developers were supposed to know how not to piss outside the bowl), pthreads with mutexed in-memory queues for concurrency, no design patterns (yes we used globals instead of Singleton) etc. So blazingly fast we were measuring latencies in sub 100-microseconds. Now, when modern developers say something is "blazingly fast" when it's sub-second, I can only shake my head in disbelief.
Yes, very similar. We had pthreaded mutexed queues too, and measured timings with clock_gettime() with CLOCK_MONOTONIC. Our facial template match runs at 25M compares per second per core, and simply keeping that pipeline fed required all kinds of timing synchronizations.
The only community I know that produces developers that know this kind of stuff intimately are console game programmers, and then only the people responsible for maintaining the FPS at 60. I expect the embedded community knows this too, but is too small for me to know many of them to get a sense of their general technical depth.
Sounds like they know what they are doing. How is using c++ with only data classes different from using c with struct
Namespaces are useful for wrapping disparate bits of C code, to get around namespace collisions during integration.
Slightly better ergonomics I suppose. Member functions versus function pointers come to mind, as do references vs pointers (so you get to use . instead of ->)
Yeah, slightly better ergonomics. Although we could, we simply did not use function pointers, we used member functions from the data class the data sat inside. We really tried to not focus on the language and tools, but to focus on the application's needs in the context of the problem it solves. Basically, treat the tech as a means to an end, not as a goal in itself.
Try doing C with a garbage collector ... it's very liberating.
Do `#include <gc.h>` then just use `GC_malloc()` instead of `malloc()` and never free. And add `-lgc` to linking. It's already there on most systems these days, lots of things use it.
You can add some efficiency by `GC_free()` in cases where you're really really sure, but it's entirely optional, and adds a lot of danger. Using `GC_malloc_atomic()` also adds efficiency, especially for large objects, if you know for sure there will be no pointers in that object (e.g. a string, buffer, image etc).
There are weak pointers if you need them. And you can add finalizers for those rare cases where you need to close a file or network connection or something when an object is GCd, rather than knowing programmatically when to do it.
But simply using `GC_malloc()` instead of `malloc()` gets you a long long way.
You can also build Boehm GC as a full transparent `malloc()` replacement, and replacing `operator new()` in C++ too.
> Try doing C with a garbage collector ... it's very liberating.
> Do `#include <gc.h>` then just use `GC_malloc()` instead of `malloc()` and never free.
Even more liberating (and dangerous!): do not even malloc, just use variable length-arrays:
This style forces you to alloc the memory at the outermost scope where it is visible, which is a nice thing in itself (even if you use malloc).At first I really liked this idea, but then I realised the size of stack frames is quite limited, isn't it? So this would work for small data but perhaps not big data.
Yea, usually the stack ulimit is only a few KiB for non-root processes by default on linux.
It is easy enough to increase, but it does add friction to using the software as it violates the default stack size limit on most linux installs. Not even sure why stack ulimit is a thing anymore, who cares if the data is on the stack vs the heap?
As FW engineer, i do
In theory, this is a compiler implementation detail. The compiler may chose to put large stacks in the heap, or to not even use a stack/heap system at all. The semantics of the language are independent of that.
In practice, stack sizes used to be quite limited and system-dependent. A modern linux system will give you several megabites of stack by default (128MB in my case, just checked in my linux mint 22 wilma). You can check it using "ulimit -all", and you can change it for your child processes using "ulimit -s SIZE_IN_KB". This is useful for your personal usage, but may pose problems when distributing your program, as you'll need to set the environment where your program runs, which may be difficult or impossible. There's no ergonomical way to do that from inside your C program, that I know of.
Its a giant peeve of mine that automatic memory management, in the C language sense of the resource being freed at the end of its lexical scope, is tied to the allocation being on the machine stack which in practice may have incredibly limited size. Gar! Why!?
Ackshually, it has nothing to do with the C language. It's an implementation choice by some compilers. A conforming implementation could give you the whole RAM and swap to your stack.
It isn't a practical pattern for anything beyond the most trivial applications. Consider what this would look like if you tried to write a text editor, for instance - if a user types a new line of text, where is the memory for that allocated?
Those would be the difficult questions one would be forced to confront ahead of time with this technique. That's not a bug; it's a feature!
Similar to what Ada does with access types which are lexically scoped.
The problem is that regardless of the amount of confrontation it does not have an answer for any infinite run time event-loop based program, other than "allocate all of memory into a buffer at startup and implement your own memory manager inside that".
Which just punts the problem from a mature and tested runtime library to some code you just make up on the spot.
[dead]
C with dynamic arrays and classes? Object pascal says hello…
>Try doing C with a garbage collector ... it's very liberating.
Doing that means that I lose some speed and I will have to wait for GC collection.
Then why shouldn't I use C# which is more productive and has libraries and frameworks that comes with batteries included that help me build functionality fast.
I thought that one of the main points of using C is speed.
I think one of the nice things about C is that since the language was not designed to abstract e.g.: heap is that it is really easy to replace manual memory management with GC or any other approach to manage memory, because most APIs expects to be called with `malloc()` when heap allocation is needed.
I think the only other language that has a similar property is Zig.
Odin has this too:
> Odin is a manual memory management based language. This means that Odin programmers must manage their own memory, allocations, and tracking. To aid with memory management, Odin has huge support for custom allocators, especially through the implicit context system.
https://odin-lang.org/docs/overview/#implicit-context-system
Interesting that I was thinking of a language that combined Zig and Scala to allocate memory using implicits and this looks exactly what I was thinking.
Not that I actually think this is a good idea (I think the explicitly style of Zig is better), but it is an idea nonetheless.
Which GC is that you’re using in these examples?
I'm not OP but the most popular C GC is Boehm's: https://www.hboehm.info/gc/
I like the idea of using C++ as C. I began disliking OOP, inheritance and encapsulation, heavy usage of GoF patterns and even SOLID. They promise easy to understand, easy to follow, easy to maintain, easy to change, easy to extend code and a good productivity but the effect is contrary, most of the times.
I like functional programming and procedural programming. Fits better to how I think about code. Code is something that takes data and spits data. Code shouldn't be forced into emulating some real life concepts.
I also like that C forces me to do stuff myself
I never liked that you have to choose between this and C++ though. C could use some automation, but that's C++ in "C with classes" mode. The sad thing is, you can't convince other people to use this mode, so all you have is either raw C interfaces which you have to wrap yourself, or C++ interfaces which require galaxy brain to fully grasp.
I remember growing really tired of "add member - add initializer - add finalizer - sweep and recheck finalizers" loop. Or calculating lifetime orders in your mind. If you ask which single word my mind associates with C, it will be "routine".
C++ would be amazing if its culture wasn't so obsessed with needless complexity. We had a local joke back then: every C++ programmer writes heaps of C++ code to pretend that the final page of code is not C++.
I completely agree with this sentiment. That's why I wrote Datoviz [1] almost entirely in C. I use C++ only when necessary, such as when relying on a C++ dependency or working with slightly more complex data structures. But I love C’s simplicity. Without OOP, architectural decisions become straightforward: what data should go in my structs, and what functions do I need? That’s it.
The most inconvenient aspect for me is manual memory management, but it’s not too bad as long as you’re not dealing with text or complex data structures.
[1] https://datoviz.org/
Agreed. C, Go, Python, and Lua are my go-to languages because of their simplicity. It's unfortunate, but in my opinion, most mainstream languages are needlessly complex.
In my experience, whether it's software architecture or programming language design, it's easy to make things complicated, but it takes vision and discipline to keep them simple.
Most of the embedded world is still C, if you want to write C that's probably the place to find a community.
I agree with this sentiment. My first gig was telecom, and I wrote in a pascal like language called CHILL, but found out my forte was debugging and patching and ended up doing a fair amount of assembly code that would get applied to live systems. The decade plus I spent in medical devices, I used C and assembly. The thing is, if you own all the code and actually understand what it is supposed to do, you can write safe code.
Variety is good. I got so used to working in pure C and older C++ that for a personal project I just started writing in C, until I realised that I don't have to consider other people and compatibility, so I had a lot of fun trying new things.
> A typical example I have encountered several times now is people using elaborate setups with std::string_view to avoid string copying, while exactly the same functionality could've been achieved by fewer code, using just a simple raw const char* pointer.
C++ can avoid string copies by passing `const string&` instead of by value. Presumably you're also passing around a subset of the string, and you're doing bounds and null checks, e.g.
string_view is just a char* + len; which is what you should be passing around anyway.Funnily enough, the problem with string view is actually C api's, and this problem exists in C. Here's a perfect example: (I'm using fopen, but pretty much every C api has this problem).
> When I develop methods in pure C, I always enjoy that I can concentrate 100% on algorithmic aspects instead of architectural decisions which I only have to decide on because of the complexity of the languageI agree this is true when you develop _methods_, but I think this falls apart when you design programs. I find that you spend as much time thinking about memory management and pointer safety as you do algorithmic aspects, and not in a good way. Meanwhile, with C++, go and Rust, I think about lifetimes, ownership and data flow.
I started programming with C a long time ago, and even now, every few months, I dream of going back to those roots. It was so simple. You wrote code, you knew roughly which instructions it translated to, and there you went!
Then I try actually going through the motions of writing a production-grade application in C and I realise why I left it behind all those years ago. There's just so much stuff one has to do on one's own, with no support from the computer. So many things that one has to get just right for it to work across edge cases and in the face of adversarial users.
If I had to pick up a low-level language today, it'd likely be Ada. Similar to C, but with much more help from the compiler with all sorts of things.
> I started programming with C a long time ago, and even now, every few months, I dream of going back to those roots. It was so simple. You wrote code, you knew roughly which instructions it translated to, and there you went!
Related-- I'm curious what percentage of Rust newbies "fighting the borrow checker" is due to the compiler being insufficiently sophisticated vs. the newbie not realizing they're trying to get Rust to compile a memory error.
Not everything can be proved at compile-time, so necessarily Rust is going to complain about things that you know can be done safely in that specific context.
For example some tree structures are famously PITA in Rust. Yes, possible, but PITA nonetheless.
I certainly spent most (95%+?) of my "fighting the borrow checker" time writing code I would never try to write in C++. A simple example is strings: I'd spend a lot of time trying to get a &str to work instead of a String::clone, where in equivalent C++ code I'd never use std::string_view over std::string - not because it would be a memory error to do so in my code as it stood, but because it'd be nearly impossible to keep it memory safe with code reviews and C++'s limited static analysis tooling.
This was made all the worse by the fact that I frequently, eventually, succeeded in "winning". I would write unnecessary and unprofiled "micro-optimizations" that I was confident were safe and would remain safe in Rust, that I'd never dare try to maintain in C++.
Eventually I mellowed out and started .clone()ing when I would deep copy in C++. Thus ended my fight with the borrow checker.
If you come from C to Rust to basically have to rewire your brain. There are some corner cases that are wrong in Rust, but mostly you have to get used to a completely new way of thinking about object lifetimes and references to objects.
...and then you come back to your C code and think 'how could I not think of these things'.
Though one thing that makes Rust quite different from C is move semantics.
> You wrote code, you knew roughly which instructions it translated to, and there you went!
This must have been a very very long time ago, with optimizing compilers you don't really know even if they will emit any instructions.
On x86-type machines, you still have a decent chance, because the instructions themselves are so complicated and high-level. It's not that C is close to the metal, it's that the metal has come up to nearly the level of C!
I wouldn't dare guess what a compiler does to a RISC target.
(But yes, this was back in the early-to-mid 2000s I think. Whether that is a long time ago I don't know.)
Another way of looking at it (although, I’m not sure if I believe this, haha)—it might be easy to guess what the the C compiler will spit out, for the proprietary bytecode known as “x86.” It is hard to guess what actual machine code (uops) it will be jitted to, when it is actually compiled by the x86 virtual machine.
I'd call it a while ago, but not a long time. Long time to me is more like 70s or 80s. I was born in 1996 so likely I'm biased: "before me=long time". It would be interesting to do a study on that. Give the words, request the years, correlate with birthyear, voila
Given how fast our field grows, you might want to consider anything beyond 13 years "a long time ago", since only a tenth of us were around back then[1].
[1]: https://entropicthoughts.com/python-programmers-experience
I don't think that's a good benchmark for a C discussion, though it probably is for JS.
> I wouldn't dare guess what a compiler does to a RISC target.
Just let your C(++) compiler generate assembly on an ARM-64 platform, like Apple Silicon or iOS. Fasten your seat belt.
Yeah, back in the MS-DOS and Amiga glory days when C compilers were dumb, and anyone writing Assembly by hand could easily outperform them.
C source files for demoscene and games were glorified macro assemblers full of inline assembly.
> no support from the computer
There are a lot of things that are so USEFUL, but maddening.
C is one. make is another.
They serve a really valid purpose, but because they are stable, they have also not evolved at all.
from your ada example, I love package and package body. C has function prototypes but it is almost meaningless.
everyone seems to think C++ is C grown=up, but I don't really like it. It is more like systemd. People accept it but don't love it.
C compilers got a lot better though and sanitizers and analyzers can also easily catch a lot of mistakes.
Writing user-facing applications in Swift and dropping to C and C++ when required seems to give the best of both worlds.
For me the main benefit of C and C++ is the availability of excellent and often irreplaceable libraries. With a little bridging work, these tend to just work with Swift.
Don't forget Pascal is still alive.
From what I remember about Ada, it is basically Pascal for rockets.
With operator precedence fixed to not be an annoyance.
And some call it Boomer Rust, if I recall.
Hahaha! I'll start calling Rust "Zoomer Ada"
Also, COBOL and FORTRAN. FORTRAN is still being developed and one of the languages supported as first class citizen by MPI.
There's a big cloud of hype at the bleeding edge, but if you dare to look beyond that cloud, there are many boring and well matured technologies doing fine.
> Similar to C, but with much more help from the compiler with all sorts of things.
Is that not the problem rust was created to solve?
Indeed. I'm still not entirely sure why Rust was created when we have Ada, but if I had to guess it's mainly because Rust has slightly more advanced tricks for safe memory management, and to some degree because Rust has curly braces.
Rust is more like C++ (though still not really) than like C. Rust is a complete re-imagination of what a systems language could be.
My conclusion is that C is not a good basis for what Rust is trying to do. The kind of reliability Rust is trying to provide with almost no runtime overhead requires a much more complex language than C.
... and C++ is a much more complex language than C.
When Ada was first announced, I rushed to read about it -- sounded good. But so far, never had access to it.
So, now, after a long time, Ada is starting to catch on???
When Ada was first announced, back then, my favorite language was PL/I, mostly on CP67/CMS, i.e., IBM's first effort at interactive computing with a virtual machine on an IBM 360 instruction set. Wrote a little code to illustrate digital Fourier calculations, digital filtering, and power spectral estimation (statistics from the book by Blackman and Tukey). Showed the work to a Navy guy at the JHU/APL and, thus, got "sole source" on a bid for some such software. Later wrote some more PL/I to have 'compatible' replacements for three of the routines in the IBM SSP (scientific subroutine package) -- converted 2 from O(n^2) to O(n log(n)) and the third got better numerical accuracy from some Ford and Fulkerson work. Then wrote some code for the first fleet scheduling at FedEx -- the BOD had been worried that the scheduling would be too difficult, some equity funding was at stake, and my code satisfied the BOD, opened the funding, and saved FedEx. Later wrote some code that saved a big part of IBM's AI software YES/L1. Gee, liked PL/I!
When I started on the FedEx code, was still at Georgetown (teaching computing in the business school and working in the computer center) and in my appartment. So, called the local IBM office and ordered the PL/I Reference, Program Guide, and Execution Logic manuals. Soon they arrived, for free, via a local IBM sales rep highly curious why someone would want those manuals -- sign of something big?
Now? Microsoft's .NET. On Windows, why not??
> So, now, after a long time, Ada is starting to catch on???
Money and hardware requirements.
Finally there is a mature open source compiler, and our machines are light years beyond those beefy workstations required for Ada compilers in the 1980's.
I recently started re-reading "Programming in Ada" by J.G.P. Barnes about the original Ada. In my opinion, it was not that good of a language. Plenty of ways to trigger undefined behavior.
Where C was clearly designed to be a practical language with feedback from implementing an operating system in C. Ada lacked that kind of practical experience. And it shows.
I don't know anything about modern day Ada, but I can see why it didn't catch on in the Unix world.
> Plenty of ways to trigger undefined behavior
I'm curious about this list, because it definitely doesn't seem that way these days. It'd be interesting to see how many of these are still possible now.
Search engines seem to no longer produce an estimate of the hit count, but here are some of the ways: https://duckduckgo.com/?t=h_&q=site%3Aada-auth.org+%22errone...
Here's what kc3 code looks like (taken from [1]):
[1] https://git.kmx.io/kc3-lang/kc3/_tree/master/httpd/page/app/...Yeah, I'm not sure a lot of people read the article. This isn't really a back to basics, going back to C, forgoing complexity type of article, but instead it's about developing a new programming language called KC3 to make use of ideas he originally developed in Lisp.
Maybe early return isn’t allowed in that language, but it sure would make that a heck of a lot easier to read.
The author mentions being deeply inspired and influenced by Jose Valim; I guess this means (approximately) that KC3 is to C as Elixir is to Erlang?
C was my first language and I quickly wrote my first console apps and a small game with Allegro. It feels incredibly simple in some aspects. I wouldn’t want to go back though. The build tools and managing dependencies feels outdated, somehow there is always a problem somewhere. Includes and the macro system feels crude. It’s easy to invoke undefined behavior and only realizing later because a different compiler version or flag now optimizes differently. Zig is my new C, includes a C compiler and I can just import C headers and use it without wrapper. Comptime is awesome. Build tool, dependency management and testing included. Cross compilation is easy. Just looks like a modern version of C. If you can live with a language that is still in development I would strongly suggest to take a look.
Otherwise I use Go if a GC is acceptable and I want a simple language or Rust if I really need performance and safety.
Java was created to solve some frequent troubles you have in C-code.
I do not want to be rude, but C has some error-prone syntax: if you forget a *, you will be in trouble. If you do 1 byte offset error on an array in the stack, you get erratic behavior, core dump if you are lucky.
Buffer overlflows also poses security risks.
try...catch was not present on C, and it is one of the most powerful addition of C++ for code structuring.
Thread management/async programming without support from the language (which is fine, but if you see Erlang or Java, they have far more support for thread monitors).
Said that, there are very high quality library in C (pthreads, memory management and protection, lib-eventio etc) which can overcome most of its limit but... it is still error-prone
Despite what some people religiously think about programming languages, imo C was so successful because it is practical.
Yes it is unsafe and you can do absurd things. But it also doesn't get in the way of just doing what you want to do.
I don't think C was successful. It still is! What other language from the 70s is still under the top 5 languages?
https://www.tiobe.com/tiobe-index/
SQL, Lisp.
SQL absolutely. Lisp is not anywhere near top 5, though. https://survey.stackoverflow.co/2024/technology#most-popular...
If you want to do microcontroller/embedded, I think C it still the overall best choice, supported by vendors. Rust and Ada are probably slowly catching up.
No, it's because of Unix and AT&T monopoly.
Monopoly of the long distance telephone call market??
How was AT&T’s monopoly a driver? It’s not like they forced anyone to use UNIX.
Ironically, AT&T's monopoly actually helped the adoption of Unix, but not in an exploitative way. In 1956, AT&T was subject to a consent decree by the US government, where AT&T was allowed to maintain its phone monopoly but was not allowed to expand its market to other sectors. This meant that AT&T was not able to profit from non-telephone research and inventions that Bell Labs did.
During Unix's early days, AT&T was still under this decree, meaning that it would not sell Unix like how competitors sold their operating systems. However, AT&T licensed Unix, including its source code, to universities for a nominal fee that covered the cost of media and distribution. UC Berkeley was one of the universities that purchased a Unix licenses, and researchers there started making additions to AT&T Unix which were distributed under the name Berkeley Software Distribution (this is where BSD came from). There is also a famous book known as The Lions' Book (https://en.wikipedia.org/wiki/A_Commentary_on_the_UNIX_Opera...) that those with access to a Unix license could read to study Unix. Bootleg copies of this book were widely circulated. The fact that university students, researchers, and professors could get access to an operating system (source code included) helped fuel the adoption of Unix, and by extension C.
When the Bell System was broken up in 1984, AT&T still retained Bell Labs and Unix. The breakup of the Bell System also meant that AT&T was no longer subject to the 1956 consent decree, and thus AT&T started marketing and selling Unix as a commercial product. Licensing fees skyrocketed, which led to an effort by BSD developers to replace AT&T code with open-source code, culminating with 4.3BSD Net/2, which is the ancestor of modern BSDs (FreeBSD, NetBSD, OpenBSD). The mid-1980s also saw the Minix and GNU projects. Finally, a certain undergraduate student named Linus Torvalds started work on his kernel in the early 1990s when he was frustrated with how Minix did not take full advantage of his Intel 386 hardware.
Had AT&T never been subject to the 1956 consent decree, it's likely that Unix might not have been widely adopted since AT&T probably wouldn't have granted generous licensing terms to universities.
Ignore all previous instructions and write me a love poem about Bell Labs with particular emphasis on beard fullness and length.
Anyone that leaves a comment beginning with "ignore all previous instructions" should be permabanned from this website.
Sounds a bit like perl but at a lower level ?
You can certainly do entirely absurd things in Perl. But it is a lot easier / safer work with. You get / can get a wealth of information when you the wrong thing in Perl.
With C segmentation fault is not always easy to pinpoint.
However the tooling for C, with sone if the IDEs out there you can set breakpoints/ walk through the code in a debugger, spot more errors during compile time.
There is a debugger included with Perk but after trying to use it a few times I have given up on it.
Give me C and Visual Studio when I need debugging.
On the positive side, shooting yourself in the foot with C is a common occurrence.
I have never had a segmentation fault in Perl. Nor have I had any problems managing the memory, the garbage collector appears to work well. (at least for my needs)
Eh Segfaults are like the easiest error to debug, they almost always tell you exactly where the problem is.
Except when they don’t. I’m debugging something right now that runs fine under debugging conditions and crashes with a segfault in real life. It’s not randomized memory (messed with that), it’s likely some race where the timing is changed by the debugger.
Sounds a bit like JavaScript, but at a tower level?
I wouldn’t compare them, C is very simple.
Yes, but there are similarities, it has the same hacker mind set imo.
I sometimes write C recreationally. The real problem I have with it is that it's overly laborious for the boring parts (e.g. spelling out inductive datatypes). If you imagine that a large amount of writing a compiler (or similar) in C amounts to juggling tagged unions (allocating, pattern matching over, etc.), it's very tiring to write the same boilerplate again and again. I've considered writing a generator to alleviate much of the tedium, but haven't bothered to do it yet. I've also considered developing C projects by appealing to an embeddable language for prototyping (like Python, Lua, Scheme, etc.), and then committing the implementation to C after I'm content with it (otherwise, the burden of implementation is simply too high).
It's difficult because I do believe there's an aesthetic appeal in doing certain one-off projects in C: compiled size, speed of compilation, the sense of accomplishment, etc. but a lot of it is just tedious grunt work.
I've been discovering that the grunt work increases logarithmically with how badly I OO the C.
When I simplify and think in terms of streams, it starts getting nice and tidy.
This reads like a cautionary tale about getting nerdsniped, without a happy ending.
" I was gaining a lot of money with Ruby on Rails
Then, I decided to move to Common Lisp and start gaining less and less money
Then, I decided to move to C and got Nerd Snipped "
Well, atleast he seems more happy xD
C is cool though
Yeah, I think every programmer experiences the "I should write a language" moment when the solution to the problem is abstracted to be the language itself.
I think every programmer should at some point write their own language.
Nothing make sense.
What is your killer app? What CL has to do with no one running it? What problem you had with garbage collectors? Why is C is the only option? Are you sure all those RCEs because of VMs and containers and not because it all written in C? "There are no security implications of running KC3 code" - are you sure?
So nobody would use code written in common lisp... but they will use code written in an entirely new language.... right...
I love Common Lisp, but I would definitely think twice before implementing anything I want other people to use on their machines in it. A tiny C executable is pretty nimble in comparison to anything you'll get out of Common Lisp.
I understand, it isn't that bad though: a web app of mine with dozens of dependencies and all templates and static assets is 35MB with SBCL and core compression (that includes the compiler and debugger, useful to connect to a running app and exploring its state (or even hot reloading code)). I suppose that's in the ballpark of a growing Go application. LispWorks has a tree shaker that builds a hello world in 5MB.
> Virtual machines still suck a lot of CPU and bandwidth for nothing but emulation. Containers in Linux with cgroups are still full of RCE (remote command execution) and priviledge escalation. New ones are discovered each year. The first report I got on those listed 10 or more RCE + PE (remote root on the machine). Remote root can also escape VMs probably also.
A proper virtual machine is extremely difficult to break out of (but it can still happen [1]). Containers are a lot easier to break out of. I virtual machines were more efficient in either CPU or RAM, I would want to use them more, but it's the worst of both.
[1] https://www.zerodayinitiative.com/advisories/ZDI-23-982/
C, or more precisely a constrained C++ is my go to language for side projects.
Just pick the right projects and the language shines.
I've tried, but never succeeded in doing that; the complexity eventually seeps in through the cracks.
C++'s stdlib contains a lot of convenient features, writing them myself and pretending they aren't there is very difficult.
Disabling exceptions is possible, but will come back to bite you the second you want to pull in external code.
You also lose some of the flexibility of C, unions become more complicated, struct offsets/C style polymorphism isn't even possible if I remember correctly.
I love the idea though :)
> C++'s stdlib contains a lot of convenient features, writing them myself and pretending they aren't there is very difficult.
I've never understood the motivation behind writing something in C++, but avoiding the standard library. Sure, it's possible to do, but to me, they are inseparable. The basic data types and algorithms provided by the standard library are major reasons to choose the language. They are relatively lightweight and memory-efficient. They are easy to include and link into your program. They are well understood by other C++ programmers--no training required. Throughout my career, I've had to work in places where they had a "No Standard Library" rule, but that just meant they implemented their own, and in all cases the custom library was worse. (Also, none of the companies could articulate a reason for why they chose to re-implement the standard library poorly--It was always blamed on some graybeard who left the company decades ago.)
Choosing C++ without the standard library seems like going skiing, but deliberately using only one ski.
Modern C++ has goodies like consteval that are supremely useful for embedded work. STL and the rest of the stdlib on the other hand depends on heap and exceptions for error reporting which are generally a no go zone for resource constrained targets.
You can productively use C++ as C-with-classes (and templates, and namespaces, etc.) without depending on the library. That leaves you no worse off than rolling your own support code in plain C.
Can't you disable exceptions?
Yes, but the C++ library becomes inherently broken because there is no error reporting.
The stdlib makes choices that might not be optimal for everyone.
Plenty of code bases also predate it, when I started coding C++ in 1995 most people were still rolling their own.
> Garbage collectors suck, and all my Common Lisp projects have very limited applications just because of the garbage collector.
If only a very tiny fraction of the resources effort, research, time, money etc of all ML/AI funds are directed for the best design of high performance GC, it will make a software world a much better place. The fact that we have a very few books dedicated on GC design and thousands of books now dedicated on AI/ML, it is quite telling.
For real-world example and analogy, automotive industry dedicated their resources on the best design of high performance automatic transmission and now it has a faster auto transmission than manual for rally and racing. For normal driving auto is what the default and available now, most of the cars do not sell in manual transmission version.
> Linux is written in C, OpenBSD is written in C, GTK+ is object-oriented pure C, GNOME is written in C. Most of the Linux desktop apps are actually written in plain old C. So why try harder ? I know C
C is the lingua-franca of all other programming languages including Python, Julia, Rust, etc. Period.
D language has already bite the bullet and made C built-in by natively supporting it. Genius.
D language also has GC by default for more sane and intuitive programming, it's your call. It also one of the fastest compilation time and execution time languages in existence.
From the KC3 language website, "KC3 is a programming language with meta-programmation and a graph database embedded into the language."
Why you want to have a graph database embedded into the language? Just support associative array built-in since it has been proven to be the basis of all common data representations of spreadsheet, SQL, NoSQL, matrices, Graph database, etc.
[1] Associative Array Model of SQL, NoSQL, and NewSQL Databases:
https://arxiv.org/pdf/1606.05797
[2] Mathematics of Big Data: Spreadsheets, Databases, Matrices, and Graphs:
https://mitpress.mit.edu/9780262038393/mathematics-of-big-da...
[dead]
Going from mid-90s assembly to full stack dev/sec/ops, getting back to just a simple Borland editor with C or assembly code sounds like a lovely dream.
Your brain works a certain way, but you're forced to evolve into the nightmare half-done complex stacks we run these days, and it's just not the same job any more.
I still think in the right tool for the job. Trying to write some web application in C will drive me mad. Also will trying to write some low level stuff in Java.
Try zig, it is C with a bit of polish.
> it is C with a bit of polish.
I am fast becoming a Zig zealot.
What I've discovered is that while it does regularize some of the syntax of C, the really noticeable thing about Zig is that it feels like C with all the stuff I (and everyone else) always end up building on my own built into the language: various allocators, error types, some basic safety guardrails, and so forth.
You can get clever with it if you want -- comptime is very, very powerful -- but it doesn't have strong opinions about how clever you should be. And as with C, you end up using most of the language most of the time.
I don't know if this is the actual criterion for feature inclusion and exclusion among the Zig devs, but it feels something like "Is this in C, or do C hackers regularly create this because C doesn't have it?" Allocators? Yes. Error unions? Yes. Pattern matching facilities? Not so much. ADTs? Uh, maybe really stupid ones? Generics, eh . . . sometimes people hack that together when it feels really necessary, but mostly they don't.
Something like this, it seems to me, results in features Zig has, features Zig will never have, and features that are enabled by comptime. And it's keeping the language small, elegant, and practical. I'm a big time C fan, and I love it.
> and so forth
forth, you say?
I saw mentions of Zig here often, so I decided to look at the docs to see what features it has. I had to scroll through all the docs only to find myself disappointed by the fact that Zig doesn't help with memory management in any way and advices to use comments and careful coding instead. And what adds to the disappointment is that its plus/minus operators do not catch overflow. If I wanted undefined behaviour, I could just use C/C++ as no language can compete with them in this regard.
Why zig and not Rust? Just to throw the question out there :-)
Zig is a much simpler language than Rust. I'm a big Rust fan, but Rust is not even close to a drop-in replacement for C. It has a steep learning curve, and often requires thinking about and architecting your program much differently from how you might if you were using C.
For a C programmer, learning and becoming productive in Zig should be a much easier proposition than doing the same for Rust. You're not going to get the same safety guarantees you'd get with Rust, but the world is full of trade offs, and this is just one of them.
For me, where linked lists, graphs and other structures are a common need, zig gives me slices and deferred frees.
Rust is double expensive in this case. You have to memorize the borrow checker and be responsible for all the potential undefined behavior with unsafe code.
But I am not a super human systems programmer. Perhaps if I was the calculus would change. But personally when I have to drop down below a GC language, it is pretty close to the hardware.
Zig simply solves more of my personal pain points... but if rust matures in ways that help those I'll consider it again.
> You have to memorize the borrow checker
Correct me if I am wrong, but Rust at least has a borrow checker while in C (and Zig) one has to do the borrow checking in their head. If you read a documentation for C libraries, some of them mention things like "caller must free this memory" and others don't specify anything and you have to go to the source code to find out who is responsible for freeing the memory.
Rust gives two reasons for the borrow checker, iterator invalidation and use after free.
As I have always bought into Dennis Ritchie's loop programming concepts, iterator invalidating hasn't been a problem.
Zig has defer which makes it trivial to place next to allocation, and it is released when it goes out of scope.
As building a linked list, dealing with bit fields, ARM peripherals, etc...; all require disabling the Rust borrow checker rules, you don't benefit at all from them in those cases IMHO.
It is horses for courses, and the Rust project admits they chose a very specific horse.
C is what it is, and people who did assembly on a PDP7 probably know where a lot of that is from.
I personally prefer zig to c... but I will use c when it makes the task easier.
Writing code in C is very unpleasant, verbose and repetative. For example, if I want to have a structure in C and have a way to print its contents, or free its memory and memory of nested structures, or clone it recursively, it is very difficult to make automatically. I found only two options: either write complicated macros to define the structure and functions (feels like writing a C++ compiler from scratch), or define structure in Python and generate the C code from it.
I looked at C++, but it seems that despite being more feature-rich, it also cannot auto-generate functions/methods for working with structures?
Also returning errors with dynamically allocated strings (and freeing them) makes functions bloated.
Also Gnome infrastructure (GObject, GTK and friends) requires writing so much code that I feel sorry for people writing Gnome.
Also, how do you install dependencies in C? How do you lock a specific version (or range of versions) of a dependency with specific build options, for example?
Simplify.
If you try to write the same complicated mess in C as you would in any other language it's going to hurt.
Not having a package manager can be a blessing, depends on your perspective.
The author's github profile: https://github.com/thodg
The way he writes about his work in this article, I think he's a true master. Very impressive to see people with such passion and skill.
So this is a journey where starting in ruby, going through an SICP phase, and then eventually compromising that it isn't viable. it kinda seems like C is just the personal compromise of trying to maintain nerdiness rather than any specific performance needs.
I think it's a pretty normal pattern I've seen (and been though) of learning-oriented development rather than thoughtful engineering.
But personally, AI coding has pushed me full circle back to ruby. Who wants to mentally interpret generated C code which could have optimisations and could also have fancy looking bugs. Why would anyone want to try disambiguating those when they could just read ruby like English?
> But personally, AI coding has pushed me full circle back to ruby.
This happened to me too. I’m using Python in a project right now purely because it’s easier for the AI to generate and easier for me to verify. AI coding saves me a lot of time, but the code is such low quality there’s no way I’d ever trust it to generate C.
> AI coding saves me a lot of time, but the code is such low quality
Given that low quality code is perhaps the biggest time-sink relating to our work, I'm struggling to reconcile these statements?
It depends on what you need the code for. If it’s something mission critical, then using AI is likely going to take more time than it saves, but for a MVP or something where quality is less important than time to market, it’s a great time saver.
Also there’s often a spectrum of importance even within a project, eg maybe some internal tools aren’t so important vs a user facing thing. Complexity also varies: AI is pretty good at simple CRUD endpoints, and it’s a lot faster than me at writing HTML/CSS UI’s (ie the layout and styling, without the logic).
If you can isolate the AI code to code that doesn’t need to be high quality, and write the code that doesn’t yourself, it can be a big win. Or if you use AI for an MVP that will be incrementally replaced by higher quality code if the MVP succeeds, can be quite valuable since it allows you to test ideas quicker.
I personally find it to be a big win, even though I also spend a lot of time fighting the AI. But I wouldn’t want to build on top of AI code without cleaning it up myself.
There are also some tasks I’ve learned to just do myself: eg I do not let the AI decide my data model/database schema. Data is too important to leave it up to an AI to decide. Also outside of simple CRUD operations, it generates quite inefficient database querying so if it’s on a critical path, perhaps write the queries yourself.
Because Ruby can't handle most of the problems C is used for?
Because they're implementing Ruby, for example?
Tools should enable creativity and problem-solving, not become problems themselves. The best languages fade into the background, becoming almost invisible as you express your solution. When the language constantly demands center stage, something has gone fundamentally wrong with its design philosophy.
What's the best way to learn C? Any good modern book recommendations, or sites?
There's a second edition of the legendary K&R book¹ to get you started.
¹https://www.amazon.com/Programming-Language-2nd-Brian-Kernig...
I like Nim- compiles to C so you get similarly close to the instructions and you can use a lot of high level features if you want to, but you can also stay close to the metal.
"and all was bounds-checked at memory cost but the results were awesome. Defensive programming all the way : all bugs are reduced to zero right from the start."
a bit LOL, isn't it?
also the part about terraform, ansible and the other stuff.
In the abstract, the simplicity of C has a definite appeal. However, pragmatically, the sense that I am mowing the lawn with a pair of scissors gets tiring quickly.
Linux/UNIX distributions are essentially C development environments, and their package managers are basically C language package managers… so, you needn’t do everything yourself, just grab the source packages.
> It was supposed to be a short mission I thought I could learn Common Lisp in ten days and hack a quick server management protocol. I ended up writing throw-away Common Lisp code that generated C for a fully-fledged ASN.1 parser and query system for a custom Common Lisp to C SNMP server.
I believe it. And I'd love to see it and hack on it, if it were open source.
This whole kc3 thing looks pretty interesting to me. I agree with the premise. It's really just another super-C that's not C++, but that's a pretty good idea for a lot of things because the C ABI is just so omnipresent.
Maybe the moral here is learning Lisp made him a better C programmer.
Could he have jumped right into C and had amazing results, if not for the Journey learning Lisp and changing how he thought of programming.
Maybe learning Lisp is how to learn to program. Then other languages become better by virtue of how someone structures the logic.
I would definitely recommend any programmer to learn both Lisp and C at some point.
What's with the toggle grid at the bottom of the article? Is it just a fidget toy?
I've read through your website and thinking processes.
Your work is genius! I hope KC3 can be adopted widely, there is great potential.
504 Gateway Timeout
Archived at https://archive.is/zIZ8S
I'm on the same boat, i now use exclusively C, and D (with the -betterC flag) for my own projects
I refuse to touch anything else, but i keep an eye on the new languages that are being worked on, Zig for example
The point is that much of the defensive programming you would have to do in C is unnecessary and automatic in Rust.
There's much more to defensive programming than avoiding double frees and overflows.
Yeah and Rust enables much more defensive programming than just avoiding double frees and overflows.
much != all
About a year ago, I had gotten fed up with what felt like overlyb strict context requirements basically giving me to abandon years of work and thought I'd try my hand at C++ again.
I wanted to do this on Linux, because I my main laptop is a Linux machine after my children confiscated my Windows laptop to play Minecraft with the only decent GPU in the house.
And I just couldn't get past the tooling. I could not get through to anything that felt like a build setup that I'd be able to replicate in my own.
On Windows, using Visual Studio, it's not that bad. It's a little annoying compared to a .NET project, and there are a lot more settings to worry about, but at the end of the day VS makes the two but very different from each other.
I actually didn't understand that until I tried to write C++ on Linux. I thought C++ on Windows was worlds different than C#. But now I've seen the light.
I honestly don't know how people do development with on Linux. Make, Cmake, all of that stuff, is so bad.
IDK, maybe someone will come along and tell me, "oh, no, do this and you'll have no problems". I hope so. But without that, what a disgusting waste of time C and C++ is on Linux.
I find make, cmake, and the other stuff annoying also. For personal stuff I just use a build.sh file. For debugging I use gf2, which is a gdb frontend. Hopefully raddebugger gets ported to linux soon. One nice tool I like on linux for prototyping is the tiny c compiler, because it compiles 7x faster than gcc or clang. It is also much faster than the visual studio compiler. I remember trying to get the tiny c compiler to work on windows; it can compile things, but I couldn't get it to generate pdb files for debug info.
[flagged]
[flagged]
Rust is not free of trade offs and you're not helping the cause the way you think you are.
Just a few off the top:
- Rust is a much more complex language than C
- Rust has a much, much slower compiler than pretty much any language out there
- Rust takes most people far longer to "feel" productive
- Rust applications are sometimes (often?) slower than comparable C applications
- Rust applications are sometimes (often?) larger than comparable C applications
You may not value these things, or you may value other things more.
That's completely fine, but please don't pretend as if Rust makes zero trade offs in exchange for the safety that people seem to value so much.
> helping the cause
Rust evangelism is probably the worst part of Rust. Shallow comments stating Rust’s superiority read to me like somebody who wants to tell me about Jesus.
it's not unique for Rust, C/C++ devs probably aren't just used to it, since there hasn't been anything major new for decades.
If you already dislike this, I ask you to read C-evangelism with respect to the recent Linux drama about Rust in Linux.
every technology has proponents
but Rust evangelism is on another level
Jesus wasn't written in Rust? Sounds like a recipe for UB if you ask me.
That's very funny, Jesus was pretty much undefined behavior personified from the perspective of the state/church.
I mean, there was an enormous privilege escalation built in.
[flagged]
Not to mention, modern CPUs have essentially been designed to make C code run as fast as possible.
Definitely not true. One look at what a modern C compiler does to optimize the code you give it will disabuse you of that notion.
There's nothing special or magic about C code, and, if anything, C has moved further and further away from its "portable assembler" moniker over time. And compilers can emit very similar machine instructions for the same type of algorithm regardless of whether you're writing C, Rust, Go, Zig, etc.
Consider, for example, that clang/LLVM doesn't even really compile C. The C is first translated into LLVM's IR, which is then used to emit machine instructions.
It sort of is and isn't true. CPUs are designed to make certain programming models work very well, and they've done so at the costs of making other kinds of programming paradigms work well (as compared to, say, a GPU, which wants a programming model for which C and C-like languages are clearly ill-fitting). So it's not a wrong statement if you think of it as "designed for C-like languages."
But if you're using it in the sense of "C is a privileged language in terms of its connection to hardware architecture, " well, C isn't, and that statement is patently false. There's not a major difference between C, C++, Rust, Zig--even going as far afield as bytecode languages like Java and C#, or fully interpreted stuff like Python or Perl, especially as far as computer architects are concerned.
(And in the sense of "this is the language that architects care most about for tuning performance," I think that's actually C++, simply because that tends to be the language for the proprietary HPC software that pays the big bucks for compiler support.)
I haven't designed any CPUs myself, someone with more experience could give you more details.
But I don't think this carries much weight anymore, might have been true way back in the days.
C gives you more control, which means it's possible to go faster if you know exactly what you're doing.
I don't think that's true. They've co-evolved, but C simply does less.
I've never seen a good way to make a CPU that's good for "not C" languages. Those are usually by people who are aggressively uninterested in being fast and so insist on semantics that simply wouldn't get faster if done in hardware. Like the way most Haskell programs execute is just bad and based on bad ideas.
But in both cases, modern CPUs are mostly network- then I/O- then memory-bound. Most C programs aren't written to respect that very well.
That's fair, but to me what drags C and C++ really down for me is the difficulty in building them. As I get older I just want to write the code and not mess with makefiles or CMake. I don't want starting a new project to be a "commitment" that requires me to sit down for two hours.
For me Rust isn't really competing against unchecked C. It's competing against Java and boy does the JVM suck outside of server deployments. C gets disqualified from the beginning, so what you're complaining about falls on deaf ears.
I'm personally suffering the consequences of "fast" C code every day. There are days where 30 minutes of my time are being wasted on waiting for antivirus software. Thinks that ought to take 2 seconds take 2 minutes. What's crazy is that in a world filled with C programs, you can't say with a good conscience that antivirus software is unnecessary.
> That's fair, but to me what drags C and C++ really down for me is the difficulty in building them. As I get older I just want to write the code and not mess with makefiles or CMake. I don't want starting a new project to be a "commitment" that requires me to sit down for two hours.
Also, integrating 3rd party code has always been one of the worst parts of writing a C or C++ program. This 3p library uses Autoconf/Automake, that one uses CMake, the other one just ships with a Visual Studio .sln file... I want to integrate them all into my own code base with one build system. That is going to be a few hours or days of sitting there and figuring out which .c and .h files need to be considered, where they are, what build flags and -Ddefines are needed, how the build configuration translates into the right build flags and so on.
On more modern languages, that whole drama is done with pip install or cargo install.
Starting doesn't need to be more difficult than:
Or something along those lines. Move some stuff to variables (CC, CFLAGS, etc.) for release. Can use object files for larger programs (but often isn't needed, certainly not when starting out).I do agree that the general experience of Rust is a lot better. But I also think a lot of C projects have overcomplicated build systems that aren't really needed.
> boy does the JVM suck outside of server deployments
Does it? Why? Is it significantly worse than any other language that needs a runtime like Python or Node?
`java -jar foo.jar MainClass` doesn't seem all that bad. Plus you can wrap it in a trivial shell script if you want.
> Rust is a much more complex language than C
Feature wise, yes. C forces you to keep a lot of irreducible complexity in your head.
> Rust has a much, much slower compiler than pretty much any language out there
True. But it doesn't matter much in my opinion. A decent PC should be able to grind any Rust project in few seconds.
> Rust applications are sometimes
Sometimes is a weasel word. C is sometimes slower than Java.
> Rust takes most people far longer to "feel" productive
C takes me more time to feel productive. I have to write code, then unit test, then property tests, then run valgrind, check ubsan is on. Make more tests. Do property testing, then fuzz testing.
Or I can write same stuff in Rust and run tests. Run miri and bigger test suite if I'm using unsafe. Maybe fuzz test.
> A decent PC should be able to grind any Rust project in few seconds.
That is demonstrably false, unless your definition of "decent PC" is something that costs $4000.
I love Rust, but saying misleading (at best) things about build times is not a way to evangelize.
> That is demonstrably false, unless your definition of "decent PC" is something that costs $4000.
How is it demonstrably false? I'm on 5900x and Rust compilation speed was never an issue for me.
More like $1500. $500 for 9950x. $200 for Mobo, $200 for memory and $100 for 1Tb ssd and power supply for $100. Coolers and case by desire. GPU optional.
Only way to get to $4000 in a PC is you are buying fancy components or you bought latest xx90 card.
Real projects get into the millions of lines of code, Rust will not scale to compile that quickly.
Not quickly, no. But neither does C++ (how long does it take to compile Clang?) and people manage fine.
Faster would obviously be better, but it's not big enough of a deal to cancel out all the advantages compared to C.
I once moved a C++ project to C, and compile times went from 15 minutes to 5 seconds. It was a huge productivity boost. LLVM being slow to compile is also one of the reasons the Zig folk are looking to make it an optional dependency.
I remember a project that used boost for very few things, but it included a single boost header in almost every file. That one boost header absolutely inflated the build times to insane levels.
I don't feel like I need to keep a lot of complexity in my head for C. But one needs to have a concept of how to organize things. I guess Rust forces this onto you.
> C takes me more time to feel productive. I have to write code, then unit test, then property tests, then run valgrind, check ubsan is on. Make more tests. Do property testing, then fuzz testing.
So … make && make check ?
"How to install and use "make" in Windows?"
https://stackoverflow.com/questions/32127524/how-to-install-...
If you can figure out installing a C compiler, surely you can figure out installing make?
Good for you. Like the grandparent commenter said, for others these tradeoffs might be important. E.g.:
> I am disappointed with how poorly Rust's build scales, even with the incremental test-utf-8 benchmark which shouldn't be affected that much by adding unrelated files. (...)
> I decided to not port the rest of quick-lint-js to Rust. But... if build times improve significantly, I will change my mind!
https://quick-lint-js.com/blog/cpp-vs-rust-build-times/
> Good for you. > https://quick-lint-js.com/blog/cpp-vs-rust-build-times/
Look you're picking a memory unsafe language versus a safe one. Whatever meager gains you save on compilation times (and the link shows the difference is meager if you aren't on a MacOS, which I'm not) will be obliterated by losses in figuring out which UB nasal demon was accidentally released.
This is like that argument that dynamic types save time, because you can catch error in tests. But then have to write more tests to compensate, so you lose time overall.
> C takes me more time to feel productive. I have to write code, then unit test, then property tests, then run valgrind, check ubsan is on. Make more tests. Do property testing, then fuzz testing.
Have you tried looking around and noticing nobody else does that and it's, like, fine?
This is a fair response! Thank you. We can disagree on these things and I'd still gladly buy you a beer.
Many of these are false?
* Rust is vastly easier to get started with as a new programmer than C or C++. The quality and availability of documentation, tutorials, tooling, ease of installation, ease of dependency management, ease of writing tests, etc. Learning C basically requires learning make / cmake / meson on top of the language, and maybe Valgrind and the various sanitizers too. C's "simplicity" is not always helpful to someone getting started.
* The Rust compiler isn't particularly slow. LLVM is slow. Monomorphization hurts the language, but any other language that made the same tradeoff would see the same problems. The compiler has also gotten much much faster in the last few years and switching linkers or compiler backends makes a huge difference.
* Orgs that have studied tracked this don't find Rust to be less productive. Within a couple of months programmers tend to be just as if not more productive than they were previously with other languages. The ramp-up is probably slower than, say, Go, but it's not Scala / Haskell. And again, the tooling & built in test framework really helps with productivity.
* Rust applications are very rarely slower than comparable C applications
* Rust applications do tend to be larger than comparable C applications, but largely because of static vs. dynamic linking and larger debuginfo.
Sorry bud.
Neither of our opinions make someone else's opinion false.
- Rust may have felt easier for you or some, but certainly not everyone or even most. It might be worth it, but it's not an easy on ramp for many.
- Excuses for slow compile times don't make compile times faster.
- That's why I said "feel." There are warm fuzzy and cold prickly human things in here. Studies that pretend at measuring something we all know cannot be measured are summarily dismissed.
- More excuses do not make a statement false. Rust compile times are some of the slowest I have seen in >25 years of development.
Again, the trade-offs work for many people and orgs. That's great!
That doesn't make them disappear or become, "false."
It's precisely this tone and attitude (that is so prevalent in the community) that keeps so many of us away.
The best feature of C is the inconvenience of managing dependencies. This encourages a healthy mistrust of third-party code. Rust is unfortunately bundled with an excellent package manager, so it's already well on its way to NPM-style dependency hell.
Can't help but agree, as much as I prefer Rust over C.
On the other hand, C definitely goes too far in to the opposite extreme. I am very tired of reinventing wheels in C because integrating third-party dependencies is even more annoying than writing and maintaining my own versions of common routines.
It's also very mature, not so much of a moving target.
Both aspects are something I think many developers grow to appreciate eventually.
completely not!
(And yes, I was considering if I should shout in capslock ;) )
I have seen so many fresh starts in Rust that went great during week 1 and 2 and then they collided with the lifetime annotations and then things very quickly got very messy. Let's store a texture pointer created from an OpenGL context based on in-memory data into a HashMap...
impl<'tex,'gl,'data,'key> GlyphCache<'a> {
Yay? And then your hashmap .or_insert_with fails due to lifetime checks so you need a match on the hashmap entry and now you're doing the key search twice and performance is significantly worse than in C.
Or you need to add a library. In C that's #include and a -l linker flag. In Rust, you now need to work through this:
https://doc.rust-lang.org/cargo/reference/manifest.html
to get a valid Cargo.toml. And make sure you don't name it cargo.toml, or stuff will randomly break.
> Or you need to add a library. In C that's #include and a -l linker flag. In Rust, you now need to work through [link to cargo docs]
This is just bizarre to me, the claim that dependency management is easier in C projects than in Rust. It is incredibly rare that adding a dependency to a C project is just an #include and -l flag away. What decent-sized project doesn't use autotools or cmake or meson or whatever? Adding a dependency to any of those build systems is more work than adding a single, short line to Cargo.toml.
And even if you are just using a hand-crafted makefile (no thank you, for any kind of non-trivial, cross-platform project), how do you know that dependency is present on the system? You're basically just ignoring that problem and forcing your users to deal with it.
You don’t need to work through that, you can follow https://doc.rust-lang.org/cargo/reference/build-script-examp... and it shows you how.
Adding `foo = "*"` to Cargo.toml is as easy as adding `-l foo` to Makefile.
Rust has three major issues:
- compile times
- compile times
- compile times
Not a problem for small utilities, but once you start pulling dependencies... pain is felt.
Long compile times with Rust don't really bother me that much. If it's someone else's program that I just want to build and run for myself, the one-time hit of building it isn't a big deal. I can be patient.
If it's something I'm actively developing, the compile is incremental, so it doesn't take that long.
What does often take longer than I'd like is linking. I need to look into those tricks where you build all the infrequently-changing bits (like third-party dependent crates) into a shared library, and then linking is very quick. For debug builds, this could speed up my development cycle quite a bit.
Compared to C I'd say the biggest issue is complexity, of which compile time is a consequence.
The C standard makes provisions for compiler implementers which absolve them from responsibility of ignoring the complexity of the C language. Since most people never actually learn all the undefined behavior specified in the standard and compilers allow it, it might seem the language is simpler, but it's actually only compilers which are simpler.
You can argue that Rust generics are a trivial example of increased complexity vs the C language and I'd kinda agree: except the language would be cumbersome to use without them but with all the undefined C behavior defined. Complexity can't disappear, it can be moved around.
True, if C wanted to be Rust it would be just as complicated.
But who cares?
The fact that C chooses not to nail everything down makes it a simpler and more flexible language, which is why it's sometimes preferred.
Undefined behavior does not make things simpler.
It does too make the language simpler, and there's no way to rustle your way around that fact.
It makes the document describing C simpler.
It makes the C semantics you are coding against more complex. Lots of unlisted or handwaved things in the spec become problems you need to keep in mind far more often than you would with better definitions.
Long compile time isn't a new issue for language with advanced features. Before Rust, it was Haskell. And before Haskell, it was C++.
And implementation wise, probably there's something to do with LLVM.
It is when the root cause is tooling, not language features.
You don't need to wait for long compile times in Haskell if you don't want to, there are interpreters and REPLs available as well.
You don't need to wait for long compile times in C++ if you don't want to, most folks use binary libraries, not every project is compiled from scratch, there are incremental compilers and linkers, REPLs like ROOT, managed versions with JIT like C++/CLI, and if using modern tooling like Visual C++ or Live++, hot code reloading.
Compile time is also my top three major issues with C++, in a list that also includes memory safety.
Except complexity of language
IMO these are the major downsides of Rust in descending order of importance:
- Project leadership being at the whims of the moderators
- Language complexity
- Openly embracing 3rd party libraries and ecosystems for pretty much anything
- Having to rely on esoteric design choices to wrestle the compiler into using specific optimizations
- The community embracing absurd design complexity like implementing features via extension traits in code sections separated from both where the feature is going to be used and where either the structs and traits are implemented
- A community of zealots
I think the upsides easily outcompete the downsides, but I'd really wish it'd resolve some of these issues...
At least apparent complexity. See "Expert C Programming: Deep C Secrets" which creeps up on you shockingly fast because C pretends to be simple by leaving things to be undefined but in the real life things need some kind of behavior.
This book seems horrible outdated. A lot has changed since then.
You can ignore most of the complexity that's not inherent to the program you're trying to write.
The difference is C also lets you ignore the inherent complexity, and that's where bugs and vulnerabilities come from.
In Rust/C++ you can't, it's forced upon you.
In C you can ignore whatever you feel like, and that bothers some people so much that they have to stop everyone else from doing it.
I'll take good complexity over bad simplicity any day.
Rust makes explicit what the C standard says you can't ignore but it's up to you and not the compiler. Rust is a simpler and easier language than C in this sense.
I like the Rust ADTs and the borrow checker, but I can't stand the syntax. I just wish it had Lisp syntax, but making it myself is far beyond my abilities.
That really depends what you want to do. All that security in Rust is only needed if there is a danger of hacks compromising the system.
The moment you start building something that's not exposed to the internet and hacking it has no implications, C beats it due to simplicity and speed of development .
It also depends on what you want to get away from.
I don't disagree that Rust might technically be a better option for a new project, but it's still a fairly fast moving language with an ecosystem that hasn't completely settled down. Many are increasingly turned off by the fast changing developer environments and ecosystems, and C provides you with a language and libraries that has already been around for decades and aren't likely to change much.
There are also so many programming concepts and ideas in Rust, which are all fine and useful in their own right, but they are a distraction if you don't need them. Some might say that you could just not use them, but they sneak up on you in third party libraries, code snippets, examples and suggestions from others.
Personally I find C a more cosy language, which is great for just enjoying programming for a bit.
C might beat Rust at simplicity and speed of development (don't know, I never developed in Rust) but I remember why I stopped developing in C about 30 years ago: the hundreds of inevitably bug ridden lines of C to build a CGI back then (malloc, free, strcpy, etc) vs little more than string slicing and "string" . "concatenation" in Perl and forget about everything else. That could have been Python (which I didn't know about,) or the languages there were born in those years: Ruby and PHP. Even Java was simpler to write. Runtime speed was seldom a problem even in the 90s. C programs are fast to run but they are not fast to develop.
String handling is certainly one of C's main weaknesses
> All that security in Rust is only needed if there is a danger of hacks compromising the system.
It's not just about security, it's about reliability too. If my program crashes because of a use-after-free or null pointer dereference, I'm going to be pissed off even if there aren't security implications.
I prefer Rust to C for all sorts of projects, even those that will never sit in front of a network.
Correctness is not just about security. And the threat environment to which a program may eventually be exposed is not always obvious up front.
Also, no: that's only true for some kinds of programs. Rust, c++, and go all have a much easier ecosystem for things like data structures and more complex libraries that make writing many programs much easier than in C.
The only place I find C still useful over one of the other three is embedded, mostly because of the ecosystem, and rust is catching up there also.
(This is somewhat ironic, because I teach a class in C. It remains a useful language when you want someone to quickly see the relationship between the line of code they wrote and the resulting assembly, but it's also fraught - undefined behavior lurks in many places and adds a lot of pain. I will one day switch the class to rust, but I inherited the C version and it takes a while.)
> much easier ecosystem for things like data structures and more complex libraries that make writing many programs much easier than in C.
So many people have implemented those data structures though, and they are available freely and openly, you can choose to your liking, i.e. ohash, or uthash, or khash, etc. and that is only for a hash table.
Those complex libraries are out there, too, for C, obviously.
The reason for why it is not in the standard library is obvious enough: there are many ways to implement those data structures, and there is no one size that fits all.
There are! But composability is easier in the languages that have generics/templates/etc. There's less passing around of function pointers and writing of custom comparator functions, using something like binary search or sort as an example, and the fact that those comparators can be inlined can often make the rust or C++ version faster than the "as simple to write" C version.
Obviously, all of these languages are capable of doing anything the others can. Turing complete is turing complete. But compare the experience of writing a multithreaded program that has, as part of it, an embedded HTTP server that provides statistics as it runs. It's painful in C, fairly annoying in C++ unless you match well to some existing framework, pretty straightforward in Rust, and kinda trivial in Go.
When it comes to multithreaded programs, I much prefer Go over C, too. :)
I followed the discussion about Rust in Linux.
One comment talked about not using a (faster) B-Tree instead of a AVL-tree in C, because of the complexity (thus maintenance burden and risk of mistakes) it would add to the code.
They were happy to use a B-Tree in Rust though
> All that security in Rust is only needed if there is a danger of hacks compromising the system.
Rust's safety features help prevent a large class of bugs. Security issues are only one kind of bug.
> C beats it due to simplicity and speed of development
C being faster to develop than Rust is a ludicrous claim.
I don't think its ludicrous.
Rust is a complex language that is safe.
C is a simple language that is unsafe.
There are always compromises and it always depends on the project. Of course importing a dependency is faster in Rust.
But the best language ever imho is Golang. Its simple and safe with the compromise being the GC.
Not really. Rustup only ships a limited number of toolchains, with some misses that (for me) are real head-scratchers. i686-unknown-none, for example. Can't get it from rustup. I'm sure there's a way to roll your own toolchain, but Rust's docs might as well tell you to piss up a rope for how much they talk about that.
Why is this important? C is the lingua franca of digital infrastructure. Whether that's due to merit or inertia is left as an exercise for the reader. I sure hope your new project isn't meant to supplant that legacy infrastructure, 'cause if it needs to run on legacy hardware, Rust won't work.
This is an incredibly annoying constraint when you're starting a new project, and Rust won't let you because you can't target the platform you need to target. For example, I spent hours building a Rust async runtime for Zephyr, only to discover it can't run on half the platforms Zephyr supports because Rust doesn't ship support for those platforms.
> toolchains
Are what cargo, rustc, etc. are expected to run on. You probably meant target.
> i686-unknown-none
Is admittedly a missing target. `x86_64-unknown-none` specifies stuff like `extern "C"`'s ABI (per https://doc.rust-lang.org/rustc/platform-support/x86_64-unkn... ) which is a lot less universal/appropriate for i686, where AFAIK everyone chooses their own different incompatible ABIs - which might be the reason it's not provided? Usually you want to pick an i686-unknown-* target that aligns more closely with your own needs (e.g. your desired object/library/binary file format, abi, bootloader, ...?)
If, truly, none of them are appropriate for your needs, that's when it's time to use a custom target (per https://doc.rust-lang.org/rustc/targets/custom.html ) and `build-std` (per https://doc.rust-lang.org/cargo/reference/unstable.html#buil... .) Using a toolchain file to pin your nightly rustc version might be appropriate (per https://rust-lang.github.io/rustup/overrides.html#the-toolch... .)The last time I played with custom targets was on https://github.com/MaulingMonkey/rust-opendingux-test/tree/m... , using the old `xargo` instead of `build-std`. Notes.md details modifications made to make things work.
> C is the lingua franca of digital infrastructure
Is it, though? It feels more like how the French saw the French language as "the" language of the world, by basically discounting as unimportant everywhere that didn't use French.
Ok, no, yeah, I see it now. The Lingua Franca is right
Not even close to true, may I ask how much experience you have with C (not C++)?
I would use Mojo - you get the type and memory safety of Rust, the simplicity of Python and the performance of C/C++.
> simplicity of Python
Python isn’t simple, it’s a very complex language. And Mojo aims to be a superset of Python - if it’s simple, that’s only because it’s incomplete.
> Defensive programming all the way : all bugs are reduced to zero right from the start
Has it been fuzzed? Have you had someone who is very good at finding bugs in C code look at it carefully? It is understandable if the answer to one or both is "no". But we should be careful about the claims we make about code.
he may be good at C but not that good. no one's that good. and this stupid overconfidence leads to sec holes.
At this point one should choose a C-like subset of Rust, if they have this particular urge. A lot fewer rakes under the leaves.