It also turns out that if you remove all the fancy abstraction and write everything out, the code becomes very maintainable and easy to manage. The fact that its about as portable as a program can be helps. I find that most people don't want to write C, but almost everyone is pretty happy interacting with C code written by others, because its simple, fast, portable, easy to integrate, and bind to other languages, and most non-C programmers can read it and have a rough idea of what is going on.
> gets out of your hair and lets you ge on with programming
This is the divergence, I think. For me, the method in which c “gets out of my hair” means that more burden is placed on me, the developer. Which means I have to work more slowly, cautiously, and remember things that a good type system could have been checking for me. And the lack of features means I spend time working around that lack rather than getting real work done.
I suspect these preferences are irreconcilable.
Why is that better though? I personally appreciate it when cleverness and convenience make my life easier, my programs less buggy and less likely to end up in a CVE, or point me toward better structure.
> It also turns out that if you remove all the fancy abstraction and write everything out, the code becomes very maintainable and easy to manage
History shows that this isn't true though, not in all cases.
Oh, C very much does have ideas. One of the big problems I have with C is that it has a surprisingly limited feature set that makes it difficult or impossible to express certain programming styles. Want to describe a function that returns multiple values? Well, you can't really do that (especially if you want this to mean "use two registers"). How about using unwind to express exceptional control flow? Can't do that either. Or if you want a function with multiple entry points? Or coroutines? C's fundamental inability to express certain kinds of control flow [1] greatly limit your ability to structure your code in ways that might make the code clearer.
> It also turns out that if you remove all the fancy abstraction and write everything out, the code becomes very maintainable and easy to manage.
I think experience has shown that this is not the case. The closest kernel of truth you might find is that if writing code requires a certain amount of tedium, that tends to limit the scale you can grow a codebase to, and size is one of the largest drivers of complexity. While it's definitely the case that abstraction can decrease maintainability, it is a mistake to think that it necessarily does so. Indeed, abstraction is often necessary to make code more maintainable. For example, when I design APIs, I think very hard about how to design it in such a way that it's impossible to misuse the API. So I make my APIs distinguish between UTF-8 strings and Latin-1 strings, so you can't be confused as to what's in them, or even distinguish between a row number and a column number.
The other thing to bring up is that we know quite well that humans are pretty bad at doing tedious, repetitive tasks with high accuracy. And computers are pretty good at that! What this suggests is that languages should be designed so that things that are tedious and repetitive--such as propagating errors up a stack, or cleaning up resources on failure--can be done by the compiler and not the programmer. As we've seen with things like gotofail, C is a language which lacks features that enable work to be pushed onto the compiler and not the programmer, and code written in C suffers as a result of that.
[1] And there's even more exotic kinds that I could get into--this is merely the list of features available in languages you've probably heard of, maybe even used!
I was with you on everything until this part. I just don't see how reducing soundness via a weaker type system, and needing to explicitly manage memory vs e.g. Rust, uniq_ptr, shared_ptr, and so on makes things easier to maintain. I also don't see how absence of generics helps here either.
Study after study has shown that, comparing development of the same application in C vs. a language with a very strong type system such as Ada, the application in the language with the very strong type system costs less to develop and has far fewer errors.
The fact that C "gets out of your hair", i.e., doesn't provide checking for certain kinds of correctness, is an anti-feature.
most non-C programmers can read it and have a rough idea of what is going on
that's because most programmers can read most other languages and have an idea what's going on. that's not at all a special feature of C.
This is much of why I prefer to use C (well, I actually prefer to use C++-as-a-better-C, but since I'll omit almost all of of what C++ brings, it's still C for all intents and purposes).
Other reasons I prefer C include producing small executables (and, more importantly, being able to effectively control executable size), that it is the most portable language (in the sense that there are C compilers for just about every computer platform in existence), and it is both readable and concise.
1. C was slightly more understandable than existing options at the time
2. The compiled objects could be manually verified/debugged with relative ease
3. There was no mystery/interpretation between the runtime and the hardware, but this isn't technically true anymore for many platforms.
4. The simplified environment demands simplified structural design. Try anything fancy, and C can be very unforgiving to the uninitiated (easier to write bad code). Do a one liner in high level languages instead =)
5. Due to the well-defined simpler syntax of standard C89, the GNU groups were able to bring opensource/free compilers to many new platforms. ARM popularity, Linux, android, and most IT infrastructure owe their existence to these compilers... even if it was just bootstrapping the new environment.
6. All languages have use-case specific tradeoffs. I have worked with over 54 different languages, and have observed the following: if your code still builds and runs on a 5 year old OS, than it will likely not need refactored for another 5 years.
As Intel begins to deprecate x86 legacy features, there will be a lot of drivers that will need rewritten.
Have a wonderful day =)
Interacting with anything but basic C is also horrible, requiring significant engineering efforts to hide the glue in a binding crate when possible, and sometimes it just doesn't work out. Many C libraries are heavy on linked lists of linked lists with their own lifetimes, and that's Rust's kryptonite.
Zig is definitely a "better-C", but also young and not nearly as widely adopted.
> why C is used in lieu of other "better-C" non-managed languages, such as C++, D, Nim, Rust, or Zig
I am a system admin. I do not earn my income writing code and therefore spend at most a few hours a week programming. I've spent about 1000 hours writing C code in my life. About 200 hours of Golang. Years of Posix Shell. Years of Perl 5. I've had a little exposure to Java and C++ and Haskell. I have read a few examples of Rust.
C++ has a higher complexity than C. C++'s syntax is more powerful, it has an additional paradigm (the C++ template system), and if you mix in QT you have one more paradigm (QT preprocessor), it has a very wide ranging standard lib (which data structure do you use for lists? vector, dequeue, ...) augmented by QT and augmented by boost. C++ is huge. There is no way I will learn that in my professional life, I simply do not have enough hours of training left. C++ is not a valid successor to C, because its complexity hinders acquiring the language. Rust suffers the same complexity as C++. I will not have enough hours on my learning schedule to acquire a proficient level of Rust.
Golang is nice for me personally. The book "The Golang programming language" is only double the size of "The C programming language", which makes them comparable in complexity. I get stuff in Golang done faster than in C since I find debugging easier.
I have neither used NIM, nor D, nor Zig. All I can tell you is that C is sexy because the language is small and therefore one can acquire it in a life time -- without being a full time programmer.
This always gets 0 comments or hundreds of comments. Some previous ones with more than a handful of comments:
* https://news.ycombinator.com/item?id=34640233
* https://news.ycombinator.com/item?id=26300199
For example (self-plug incoming) we're hosting a workshop this summer [0] teaching people to replace a rat's nest of mallocs and frees with an arena-based memory allocator.
These sorts of techniques eliminate the common memory bugs. We don't do C programming the way we did back in the 80's and 90's.
Using Malloc and free in the naive way was the exception, not the rule
They can be the cause of memory safety problems in some cases, as well as a partial solution in others
However, the author themselves state that this is not something that implies that undefined behavior are unavoidable when working close to the metal:
In fact, the very “unsafety” of C is based on an unfortunate conflation of the language itself with how it is implemented. Working from first principles, it is not hard to imagine a safe C.13 As Krishnamurthi and Felleisen [1999] elaborated, safety is about catching errors immediately and cleanly rather than gradually and corruptingly
I would say that, what they are describing is essentially what Rust has achieved, or at least what it is meant to do.I have two responses to this that aren't "back on script."
> it is not hard to imagine
(from your quote)
Sure, we can imagine this language, but it does not exist. Yes in theory maybe if we all did what the author said, C could be that language, but it is simply not today. When people say "C" they mean the C that actually exists, not an alternative implementation that would technically conform to the standard. Pushes have been made in this direction a number of times, from a number of different people, yet the market has spoken.
Second,
> Consider unchecked array accesses. Nowhere does C define that array accesses are unchecked. It just happens that implementations don’t check them. This is an implementation norm, not a fact of the language.
This is just factually incorrect. Let's examine both C89: https://www.open-std.org/JTC1/sc22/wg14/www/docs/n1256.pdf and C99: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
> J.2 Undefined behavior
> An array subscript is out of range, even if an object is apparently accessible with the given subscript (as in the lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.6).
It is literally defined as undefined behavior. While sure, that does mean a particular compiler could implement and document semantics for this, it's by definition not portable, because it is stated as such by the standard.
Based on a true story: https://en.wikipedia.org/wiki/Adelir_Ant%C3%B4nio_de_Carli
C is small, with very few hidden 'gotchas', and it works. No wonder I always come back to C. You could call it the WYSIWYG programming language.
let variable_name : type = value;
I find it extremely ugly.I know that people here have disdain for discussions about syntax [1], and I know that once compiled, the specific syntax doesn't matter that much.
But I think that language aesthetics do matter.
People say that languages are "just tools", and the output quality is all that matters. But, personally, I would want the tool that I'm going to be spending a significant amount of time working with to be beautiful and feel good.
And that's one point where I consider C to be superior: aesthetics. If I start a hobby project, I may start in C, because programming in it makes me feel good, even if it's unsafe.
I'm not a language expert, so I don't know if there are good technical reasons for this modern syntax to have replaced C-style initialization, but I think there must be some way to avoid using this modern syntax without losing whatever feature is made possible by it.
So, my point is: We shouldn't discard aesthetic considerations so lightly. Languages are not "just [ugly] tools", they can be a medium of artistic expression to some people.
- The variable name comes before the type. Thus if the type name is large it makes it difficult to scan for the variable. This isn't too bad for `let`s in a language like C, but my day job is C++ right now and the fact that the return type for functions comes first is terrible. It's inevitably enormous, so it makes it bloody impossible to quickly see what the damn function is called.
- The keyword `let` is first, so with syntax highlighting you can easily visually see the structure of the code, and which lines declare variables vs. call functions.
Are there reasons to like the C style
type variable = value;
besides familiarity, and it being slightly shorter?1. It doesn’t require as much context to parse, making tooling easier.
2. It plays nicer with type inference or deduction.
These reasons generally trump aesthetics for new language developers, and so I wouldn’t expect to be seeing the C style in newer languages. Also, not everyone agrees aesthetically; I am the polar opposite of you.
type variable_name = value;
style, but I do like the type coming afterwards. I don't feel like there's a tradeoff, I think it's just better across the board, and I assume a lot of people are in that boat as well due to its popularity.Right now, C++ is my language of choice, but I'm far from happy with it, for obvious reasons. I think my ideal language would be as close to C as possible while adding some quality of life modifications. Off the top of my head, those would include: namespaces, modules, generics, removal of the -> operator, and removal of forward declarations (which would imply order-independent declarations).
Of course, it's easy to imagine such a language, but hard to implement it and be successful in practice. Most attempts probably never get out of the hobby project phase. Those that achieve some success will feel pressure to continue to add features to differentiate themselves from the competition. Maybe I am in too small a niche and there simply isn't a market for what I would like to use.
probably best avoid for the first few years of serious engineering as a beginner.