Exploring the Replacement for C as an Educational Language
Exploring the Replacement for C as an Educational Language
When I created my C Course it was with a few specific purposes in mind:
- Learning C is like learning Latin. So many languages inherit features and flaws from C that learning it gives you an easier time learning other languages. Do you want to learn Rust? Well, C is the reason Rust has many of its features and syntax. Want to learn JavaScript? Well, C is the reason JavaScript has 10 different for-loops (that all don't really work right).
- It's a simpler compiled language. Many programmers who come from languages like Ruby, JavaScript, and Python have a significant learning curve when they switch to a compiled language. The additional steps, the concept of source files creating an executable, linking libraries, missing
.dll
files, and other difficulties are easily explored in C. Only a simpleMakefile
and basic compiler options teaches most of what you need to know. - C memory management is the Giedi Prime of Programming. Tiny little Dune reference for you, but C's memory management is so raw, hostile, and bare that surviving it makes you extremely good at memory management in other languages. You also learn to appreciate why no other language uses C's memory management and style of strings. When you combine C with constant Valgrind use you learn so much about how memory is really utilized--and abused--in software.
Despite all that, I have problems with people learning C for these purposes. Sure, if you learn C you can level up your skills, but do people really have to struggle with this nonsense to learn these things? Countering my reasoning for learning C are these criticisms:
- Learning C is about as useful as learning Latin. Et tu, Brute? You probably only know that phrase because of Shakespeare's play Julius Caesar, and I'm willing to bet only a fraction of a percentage of you can actually speak Latin. I bet there are more people who speak Esperanto today. It is simply not a useful language to learn, even though learning Latin makes many other languages easier to learn, even Esperanto. C is quickly trending toward this same fate. With the rise of Rust, Go, Zig, Crystal, Nim, you'll start to see the reasons to use C dwindle even more. Quite honestly, if you try to think of a project that C is great for, there's potentially 3 of these other languages that are better suited.
- It's too simple. Outside of pointers and manual memory management what concepts do you learn that you don't get in a language like JavaScript or Python? You don't learn Object Oriented Programming. You don't learn how to use built-in data structures (think
list
anddict
in Python). If you want a list you write your own, which can be a useful exercise, but nobody writes their own lists anymore. About the only generics you deal with are my favorite hacks utilizing the C pre-processor. - C memory management is simply too harsh to learn the topic. I still think it's useful to struggle with C's memory management to learn about how to never repeat its mistakes, but do people really have to use C to learn those concepts? You could probably learn the same things with Rust or Zig, since they both feature "manual" memory management, but with a lot of safety features. However, I feel those languages might make it too safe to teach why they have those safety features. I feel like you need a bit of danger to experience what's bad, but C is simply too dangerous to be useful.
These are just some of the reasons I started looking for an alternative to C as my "intermediate" programming course. Sure, learning C will teach you a lot, but there has to be a language that does the same thing but in a better environment with a more practical outcome.
The Feature Requests
Let's review the tail end of that last sentence to think up a list of criteria for a replacement C:
- "teaches the same things as C" For a language to be viable as an alternative to C for this purpose it has to feature manual or semi-manual memory allocation and management. You may disagree with me, but I believe actually performing memory management manually teaches people how it works even in languages that do it automatically. This makes people better programmers since "where stuff lives" goes from an abstract to a concrete concept. I also think the syntax has to be similar to C, with the
{}
blocks and all that, since so many languages use that syntax. That particularly quirky syntax with the;
endings and the odd optional{}
shows up everywhere (usually for no reason other than it shows up everywhere). Pointers or at least the concepts of reference vs. value should be featured in the language, but I really think C's pointers and manual memory management are too far on the dangerous end of the spectrum. I want a bit of danger, or the option of danger, but not C's "haha you are a loser who'll never be good enough" attitude toward pointers. - "in a better environment" When I say an "environment" I'm not really talking about tools or ability to code in C. C's not too bad when it comes to compiling on different machines or finding good tools. When I say "environment" I mean that the tools and community available aren't violently opposed to helping you avoid errors. C programmers and the tools they use seem to simply hate people. My favorite example of this is how the
-Wall
option to most compilers--which should turn on all warnings--does not turn on even close to all warnings. Or, how C compilers are amazing at detecting undefined behavior...so they can exploit it silently rather than warning you about it. At the same time, I sort of feel struggling with terrible error messages and bad tooling is an important learning process for programmers today. I wish this wasn't the case, but an important milestone for beginners is the realization that error messages are next to useless. - "with a more practical outcome" My biggest problem with C today is that you just can't do much with it. When I say that C fans will revolt claiming, You can totally write MacOS apps in C but there's different levels of "can do." Can you create a video game in C? Totally. People have created video games in every language ever. Is it easy to make video games in C? Not compared to C++ or C#, because there's large ecosystems that already exist for making games in those languages. Can you create a network server in C? Totally, but not as easily as you can in Rust or Go. Can you make a programming language in C? Obviously, but not as easily as you can in C++, Rust, Go, or Zig. When I think of a practical outcome I think, "What can a person who has gone through my course then go on to easily learn or do next?" When they learn C...they can mostly go learn something else that'll be easier? Not much else comes to mind in today's world.
The Windows Monkey Wrench
Let's take one of my statements before regarding C and throw a monkey wrench into a rebuttle I know I'll receive: "You can't easily make games in C." There exists quite a few C game dev libraries like SDL2 and Raylib. If I think of easy game libraries in C the one that comes to mind is Raylib. Sure, you could make a game reasonably well in Raylib. It has tons of features. Looks like a real winner.
Alright, monkey wrench time: Get Raylib to work on Windows. Last time I tried this nothing worked. It wouldn't compile with mingw, cygwin, or Visual C++ because it had tons of extra libraries required to work. There was no simple installer that just installed everything so you could use it right away. The instructions have you using vcpkg which is an actually viable method, but every time I tried it the instructions failed. It's a total mess.
Let's compare that to building a game with Godot, Unity, Game Make Studio, Unreal Engine, and Defold:
- Download Godot 4 for Windows which supports GDScript, C#, and various languages.
- Download Unity for Windows which supports C# and other .NET languages.
- Download Unreal Engine 5 for Windows which supports C++ (and others?)
- Download Game Maker Studio for Windows which supports GML which seems to be a kind of JavaScript.
- Download Defold for Windows which supports Lua.
This is what I talk about when I say "easily do." I don't mean, "You can code a game in Raylib after you've spent 2 weeks fighting package managers and dll hell." I mean, "You can make a game easily in Unreal Engine by downloading the installer and...making your game." You can also find tons of tutorials on all of the above game engines. If you want to make a Raylib game well, better be prepared for the typical open source documentation that painstakingly describes everything but doesn't tell you how to do anything.
Evaluating Various Languages
If I make a big list of compiled languages to replace C for the course it would probably look like this:
- Rust
- Zig
- Go
- C++
- C#
- Crystal
- Nim
I can then come up with a set of criteria to evaluate each one and eliminate various ones:
- It should feature manual memory management of some kind. While I think 95% of programming is better done in a language with a Garbage Collector, this is an educational requirement so people learn where their bacon actually comes from. This rules out Go, C#, and I think Crystal. I know Nim is fairly flexible in memory options.
- It should have a C-like syntax. That rules out Nim and Crystal.
- It should be dangerous, just not C dangerous. One reason to learn a bare metal compiled language is to learn all the ways bare metal code dies in horrible fires lit by morons who trust the language. Remember that a big component of the C book is defensive programming and why things like C's strings were a massive disaster for all human kind. This rules out Nim, Crystal, Go, and C#, but weirdly for all the right reasons. Rust is interesting because you can use it in dangerous ways, but it makes it difficult, which could be educational too.
- It should have many concepts found in other languages. I think most of these languages have things like generics, something like OOP, modern data structures, good safe strings, etc.
- It should be easy to debug and test. I think Zig is a big winner here since that project baked testing into the language very well, but I believe the debugging criteria might rule out Nim and Crystal. I'd have to test how easy it is to use a step-through debugger on them to watch code work.
- Finally, you should be able to make a game with it. I would like to have the next course aim for game development, or something along the lines of, "Enough language basics that you can get into game development." This is partially because I'm also interested in game development, but also because I have a strong suspicion that it's the future of programming. All of the languages are supported by SFML. Zig has the Mach Engine which looks viable. Rust as Bevy which is also viable. Obviously C++ is the absolute king of this realm.
After all of the eliminations we're down to just Rust, Zig, and C++. I could conceivably do a course on each but let's do some more analysis to narrow down to just one as the first candidate:
Rust
- Rust has a kind of manual memory management but it's heavily managed by the compiler. I originally threw Rust out of the running because it is such a difficult language to deal with but then I read this nice post about smaller Rust that seems to provide an avenue to start simple, and gradually add in Rust's more difficult memory semantics.
- Rust definitely has a C like syntax.
- Rust is a fairly safe language, but it looks like you can make it dangerous enough to study the effects of C's bad decisions.
- Rust seems to have all the concepts found in other languages like generics and types, so probably OOP isn't too far from there.
- I'm not sure about Rust's debugging quality, but I believe their error messages are quite good. This would require more research.
- It looks like you could make games with Rust, although I'm not sure how difficult that is on Windows. It should at least be able to use SDL2 or SFML for some basic games.
Zig
- Zig has a very reasonable managed memory system. It's not as bare as C's, but not as managed as Go's.
- Zig also has a very C like syntax.
- Zig is also a safe language, but I'm actually not sure how it compares to Rust's safety. I also don't know if you can make Zig "dangerous" enough to demonstrate memory safety.
- Zig seems to have generics as well as types, so like Rust it's similar to having OOP. Keep in mind I'm not a big OOP fan, but you find something like types/classes/objects in so many languages it's useful to learn them.
- I'm also not sure about Zig's debugging, but I do know they've baked testing into the language in a very nice way. They also use tests extensively in the documentation.
- Zig seems to be very good at interfacing with other languages, so probably the way to make games with it is use something like SDL2 or SFML to do basic games.
C++
- C++ definitely has a wide array of manual memory management facilities. It's possible that it has too many to be useful, so I may have to figure out what's the most straight forward today, and then show people how it goes horribly wrong.
- C++ obviously has a C like syntax, and easily the most C like syntax of the three.
- C++ can be more safe, but it requires some discipline. I feel like C++ has been lumped in with C wrongly, because the memory safety exploits I've seen have been in code bases that use C++ as a "better C." These projects tend to not use any of the memory management facilities that C++ has to avoid problems, still use the jank C strings, and swizzle pointers all over the place. This means that C++ is better than Rust or Zig for learning "bad memory practices" since it's easy to demonstrate how it's done wrong, but it's not as ruthlessly brutal as in C.
- C++ has the granddaddy of all generics systems, although I feel it's horribly overused and abused. I think if I get into templates I'd avoid most "meta-programming" uses of it.
- I know C++ is debuggable, but not really sure of the available testing frameworks. I also like the idea of still using Valgrind to teach good memory practices, but many compilers for C++ now support advanced memory sanitizers.
- The one massive advantage C++ has is you can DEFINITELY make games with it. I'm still thinking doing dinky little games with SDL2 is the way to go, but I'd investigate available frameworks to see what's easiest. Weirdly, C++ isn't listed on the SFML supported languages page, but it must be supported.
More Testing Required
Based on this analysis I've decided to test out C++ for the C replacement course. I've spent the last week studying the crop of C++ build and package systems for Windows. I'll have a new blog post on what I've found, but so far Meson is holding up as a good cross platform build system with package management features. I can create simple programs easily, build them, and even got a their SDL2 Demo working with only a few minor gripes. The next test will be the following:
- How hard is it to replicate this Meson SDL2 build on OSX and Linux? The rationale behind this is it's a good test of the build environment since SDL2 is notoriously frustrating to get working on multiple platforms the same way.
- How massive is C++ now? Can I re-learn a modern subset of C++ that's still useful or is it such a massive pig now that no human could ever use it?
- What's the best compiler for all the main platforms? Is it clang or should I just go with the best one for each platform?
- Is trying to use Meson so the course works everywhere a lost cause? Could I just give up and feed people into Microsoft's IDE Sarlacc.
- Is SDL2 good enough for some basic games, or is it too hard core? Maybe SFML is better? Something else? I like the idea of SFML because it's possible to use it in multiple courses for different languages.
- Does C++ support enough nice memory management to not be too brutal? Or, is C++ just as lost of a cause as C even today?
If you have some opinions or more information on the above, feel free to email me at help@learncodethehardway.com. I'm especially interested in blog posts demonstrating your favorite language's ability to meet my requirements.
More from Learn Code the Hard Way
Rogue is the Best Project
My pitch for Rogue being the best for both beginners and old crusty coders like me.
Very Deep Not Boring Beginner Projects
A list of projects that anyone with basic programming knowledge can do in any language, but have a lot of depth and aren't boring to do.
C++ Is An Absolute Blast
Where I try to explain why I feel like C\+\+ is so much fun, and to correct some misinformation.
Just Use MSYS2 You Say?
Debunking idiots on HackerNews who think a total beginner can just 'Use MSYS2' on Windows.