I thought I was building a C replacement. I was wrong

37 points by clerno 5 hours ago on lobsters | 27 comments

[OP] clerno | 5 hours ago

TLDR; people think that a "C-alternative" means "used for what C is used for today". But that's not what the C alternatives (except for Zig) try to achieve.

JulianSildenLanglo | an hour ago

Huh, yeah that's a good clarification because I definitely assumed it was for replacing C in microcontrollers and other resource constrained systems.

hoistbypetard | 5 hours ago

While I can't speak to C3 or how you've marketed it, really, because this is the first I've heard of it, your observation about replacing C really resonated with me.

I had only kind of noticed the shift, and would've interpreted your marketing the way you intended it. C was the second or third language I learned, after BASIC, depending on whether you count LOGO as a language. And it really served an application language. I knew that was much less common now, obviously, but I hadn't really observed that as a real change in its role.

[OP] clerno | 5 hours ago

It's easy to get it wrong, because you're getting users for which C is already something attractive. They're not going to tell you the messaging is wrong, because they're the ones who got it.

I only realized this when I started to understand why Zig was different from Odin and C3: Zig was targeting what C is used for today, while Odin and C3 were aiming for general application programming (not to say Zig can't be used for it, but just like C of today - it's not the focus). From that perspective people saying "Zig is more C than C" suddenly made complete sense.

And this is what led me to the revelation (for me) that when you say "C alternative" that means something else to most of today's programmers.

malxau | 2 hours ago

At that time, the most difficult thing was actually finding a compiler if you were young and had hardly any budget.

Not really the point of the article, but this really resonated. Many people today who use gcc or clang today may not realize that in the 80s getting an optimizing compiler was expensive, and a non-optimizing compiler produced very bad code compared to assembly. Turbo Pascal did well because hobbyists could afford it and it optimized.

It's actually noteworthy how much gcc had to do with making C truly mainstream, and that C's success was not guaranteed. Most OS or embedded code in the 80s was assembly, and applications could be written in Anything (tm.)

These days I'm buying those old compilers on eBay just to learn the "high end" tools that I didn't have access to at the time. And even with optimizers, you really need to, uhh, help them optimize.

[OP] clerno | 37 minutes ago

Yes and don't forget how the first MacOS was Pascal + asm!

nortti | 2 hours ago

The methods are fundamentally linked to this problem. As soon as we write:

foo.do_something(bar)

we have created a hierarchy where the class of foo is more fundamental than bar. […]

The problem with OOP — or, if I may spread the net wider, "methods first" — is that the up-front architecture and the use of methods inhibit these changes.

As an aside, while this is true with the usual single-dispatch approach, some languages support multiple dispatch. Common Lisp Object System is a classic example, but nowadays Julia might be the most popular take on it.

[OP] clerno | 19 minutes ago

Multiple dispatch is super cool. I haven't used it enough to say anything about how it affects the design and architecture, so I'll leave it at that :D

Another part of being a C replacement is the fact that after a defined "1.0" version it must be backwards compatible till the heat death of the universe.

Zig is written with this in mind, which is why it makes so many breaking changes: Neither language nor stdlib api can change after the language was released as a "1.0".

Languages like C persist because your code from the early 90ies is still usable and maintainable today. If you want to be a true alternative, this stability guarantee should be part of the plan

[OP] clerno | 4 hours ago

That's not quite true for C though. C is slowly shedding some features, e.g digraphs and trigraphs. And interestingly Zig wasn't originally planned to stop at 1.0, but rather 2.0, 3.0 etc was planned (see early issues on Zig).

The only language which I've seen being explicitly dedicated to the goal of "1.0 and no breaking changes after that" is Hare, not Zig.

In any case, even though C3 1.0 is a stop point for the language with no further changes planned, I feel I must push back a bit on the idea that this is something that completely defines C.

It is a nice property of C, but as we see in plans for future C versions, there are pushes to more aggressively deprecate features and add things that don't match C (the early version of defer of example – not to be confused with the one which is getting added).

I personally reject the idea that you should constantly add features in the way that is currently in vogue. However, I am also somewhat careful of making categorical statements. What if, some years later, the language has a deep and fundamental flaw that couldn't be predicted? Should the language then never adapt to this?

muvlon | 3 hours ago

Right, C's level of backwards compatibility is very high but not infinite. It is shedding features decades after they fall out of use. To get to that same level, a new language has to become decades old first, for starters.

And also yes, I agree that some things are too flawed even for C and need to be broken or removed. It's good that gets() is gone as of C11 for example.

C is slowly shedding some features, e.g digraphs and trigraphs.

To be clear, digraphs were introduced by C++98 and C99 to replace trigraphs (digraphs don’t have the obnoxious rewrite semantics of trigraphs).

Another example is gets() which was dropped before trigraphs.

Yes, but C has a huge disadvantage:

it came from a time where the empirical data of good and bad language design ideas was still fairly low.

we know have much more knowledge. "everything nullable" is rolled back by (afaik) java and c#, c++ also tries to get away from all the weird pointer semantics by getting clearer pointers, c does so as well.

optionals, tagged unions, sum types, ... are all coming to many languages as they have provem themselves to be great.

C has gotten a defer inclusion as it has proven itself to be great.

C has the burden to come from a time of less knowledge and experience.

new languages habe the advantage of knowing which ideas where shit and which not.

and: there's nothing bad about releasing new versions, but if we'd stick to semver, some C standard releases are minor bumps (ipdate and you won't even notice) and major (youe code may break).

this will likely be true for C3 and Zig as well. the good thing is that we can use semver or similar for language versions as well, and thus can communicate potential breakage trivially to the user.

[OP] clerno | 40 minutes ago

I largely agree, but I think we should always be a bit careful about declaring "we have much more knowledge". We do know more, but fads are very much alive in language design today.

For example, it's not uncommon that some feature is made popular by some language and then almost every language needs to add it in order to stay "modern" and "relevant" and to damn with whether it actually fits that other language.

Language adoption is also not driven by "what's objectively the best language", but rather by other considerations, from "access to developers" to "what it hyped and makes investors interested"?

Furthermore language design is more an art than engineering still. So it's not necessarily an exercise in logically picking the best features from what we know.

hawski | 3 hours ago

I remember reading about Zig having some tools in the future to automatically port (or help porting) old Zig code to newer Zig versions. I think (though less sure about that) same thing is also being thought for Hare.

zig fmt does that, but only for unambigious changes, and the results sometimes look rather crude.

it also does not adjust the code to changes in the stdlib

dominicm | 2 hours ago

Clojure is dedicated to the backwards compatibility goal very firmly. Following in the footsteps of Java.

chbarts | 54 minutes ago

More to the point: C is from an era when languages had actual written-down standards both implementers and developers could point to to say "This is how it works in this version of the language, defined by ANSI/ISO standard... " and if you wanted to diverge from that, like the GNU people do with GNU C, you mention it explicitly in documentation. Standards change slowly, but they do change, and old standards continue to be available.

With modern languages, there's One Blessed Implementation and whatever that does is correct by definition. Only Perl Can Parse perl as the saying goes. Non-standardized languages can have versions and versioned compatibility but there's no external body maintaining an independent source of truth for the language, such that anyone could rock up and implement it with no reference to the source or behavior of another implementation.

[OP] clerno | 38 minutes ago

Hare started with a spec first. C3 has a spec as well. The criteria for Odin 1.0 is going to be having a spec. So I don't think "possible to implement from a spec" is really forgotten. At least not when it comes to Hare/C3/Odin.

icefox | 5 hours ago

(And this is also why Zig's decisions are so different from C3's: Zig's primary goal seems to be to replace C where it's used today.)

Very cogent observation!

hyperpape | 3 hours ago

It’s strange to me: what is it about OO that it motivates people to adopt such strong “No True Scotsman” views? I write Java[0] in my day job and a lot of my hobby projects, but I freely adopt more or less procedural or functional approaches depending on what I feel makes sense for the context (and my view about which contexts require which approaches still isn’t fully settled after over a decade).

[0] Inviting the inevitable “Java isn’t really OO” no true scotsman takes.

icefox | 2 hours ago

It's a result of huge amounts of marketing from the 1990's and 2000's, which built off of both the fear of an oncoming software apocalypse due to the perceived failure of traditional/structured programming to keep pace with the demands of software, and the 1970's/1980's Unix counterculture which was full of "no true scotsman" vibes anyway. So you got billions of dollars of holy war stoked by different software vendors, none of which really had particularly revolutionary products in retrospect.

Or maybe not, I'm just an idiot with an opinion.

[OP] clerno | 49 minutes ago

I think you are doing like me. Don't get me wrong — I'm fine doing Java, it's easy. But what I realized is that I basically internalized the cost in upfront architectural design. The way we get better at OO is getting better at guessing the correct structure up front. But we still pay in many ways in extra code just for doing OO.

And the promise of OO was that it was going to be more extendable, more easy to refactor because we paid this cost. This, however, was largely wrong. By encapsulating state, the promise was that state was going to be manageable. What this failed to account for is that by dividing state and still having an object graph, we're not actually encapsulating state, we end up distributing it, which is making it worse.

Naturally it will differ by project and by author, but the observation is that the promise of OO is largely false.

This is not going into OOD, which is another thing and is just plain wrong (you can use OOP without doing full OOD, so that's an important distinction)

gulbanana | 2 hours ago

You're not alone - the Go developers conceived of themselves as building a "better C" too, which caused some confusion at first since the software they aimed to replace was mostly written in Java.

accelbread | 2 hours ago

As I write my own C alternative, I'll keep this in mind. I think my own definition is closer to the authors, though I learned C when it was already relegated to niches. With the perspective from the post, I'd assume others would also see mine as more of a C++ replacement as well.

kristoff | an hour ago

Zig's primary goal seems to be to replace C where it's used today.

Zig's goal is the one stated on our official website:

Zig is a general-purpose programming language and toolchain for maintaining robust, optimal and reusable software.

None of what is written there corroborates the interpretation given in the blog post nor what has been argued in comments:

while Odin and C3 were aiming for general application programming (not to say Zig can't be used for it, but just like C of today - it's not the focus)

If replacing C "where it's used today" was the main goal, we would be recommending to rewrite everything in Zig instead of creating things like https://github.com/allyourcodebase.

The whole point of AYC is to leverage the existing C ecosystem to build new software that can depend on pre-existing C code in a robust manner (instead of jerry-rigging a build pipeline that adds more system dependencies, and that might or might not work on the platforms you know less about).

[OP] clerno | 20 minutes ago

Do you see the difference in Zig's message "general-purpose programming language" and the quote's "general application programming". The key is in "application". Why isn't C popular for that (consumer apps and games) today and does Zig address those problems?

I always thought Zig's decisions on explicitness an odd focus, but it is explained if you, as another Zig user said, see it as "a language more C than C". It's "we want more details than C", you get that in Zig:

More precision in casts, in pointer types, in memory management, in IO handling.

But getting good for general application programming is about being able to quickly address the main functionality of the app. That means: more abstractions, more ergonomics. Exactly what Zig is opposed to. Zig wants to be more explicit with fewer abstractions, and ergonomics is second to this explicitness as well.

The Zig bet is that by having more explicitness you will get something "optimal, robust and reusable".

But the general application often doesn't care about "optimal, robust and reusable" that much. It's about delivering the application in the hands of the user and allow them to achieve what they want.

Time isn't infinite, if the programmer spends a long time on the explicit code and this makes the code better, then that still means less time for things like features that the user also cares about.

It's a trade-off. C3 doesn't walk all the way to something like Python, but it still tries to offer same or more conveniences as C++ or Swift would. That's a trade-off, because going more to Python/Ruby would make it more high level but also less performant and less possible to do low level code.

If Zig wants to be nicer for applications then it needs more ergonomics and abstractions, but then it can't be the explicit, detailed low level thing it currently is.

Examples: Zig stdlib removes managed dynamic arrays – more detail yes, less ergonomic and less easy for general use. Zig doesn't have operator overloading – more explicit yes, but less ergonomic and easy to use. I could go on. It's just different trade-offs and you can't say Zig both is explicit and has comparable abstractions, because it doesn't.