Hacker News new | ask | show | jobs
by pjmlp 12 days ago
Go is a better C already, designed by C authors themselves, with other UNIX key figures.

Which while some of the design decisions might be debatable, they actually knew what C is all about, informed by their own experience with what worked in C, Alef and Limbo, across UNIX, Plan 9 and Inferno.

6 comments

> Go is a better C already

It’s not. Garbage collection made sure of it. I believe everyone agrees that a "better C" has to have manual memory management. Most even rule out Go as a systems language because of GC.

Of course, I’m pretty sure Go is much better than C at some problems. But there’s no way in hell it supersedes it.

Nope, only the anti-GC religion would agree to that.

Most of those folks would never manage to replicate something like Xerox Cedar on their own, from 1980 in their beloved 2026 computers.

Thankfully we have the likes of Apple and Google, that have a my way or the highway education system for such developers, that want to go through their magical gardens.

There are roughly two orders of magnitude more microcontrollers in the world running code than application processors. GC is not acceptable on a microcontroller due to the extreme resource constraints. The GP post is correct, Go can replace some use cases for C, but it does not replace all use cases and GC is part of the reason.
> GC is not acceptable on a microcontroller due to the extreme resource constraints.

Dynamic memory allocation on a microcontroller can be treacherous full stop, but if you have accepted the tradeoffs of using dynamic memory allocation then why not GC? You wouldn't want gc[1]-style GC, but there are other GC algorithms that can work well enough on micros. The Go spec calls for garbage collection, but it doesn't say how you have to collect garbage.

> but it does not replace all use cases

It could replace all use cases. There are arguably much better tools for many jobs[2], but if you had to the language isn't going to stop you.

[1] gc the compiler, not GC garbage collection. Naming is the hardest problem in CS.

[2] Some would say all jobs, but for the sake of discussion let's agree that Go can be a reasonable choice at least sometimes.

> * but if you have accepted the tradeoffs of using dynamic memory allocation then why not GC?*

That’s the thing: many do not use dynamic allocation at all. Did you know for instance that you could write an entire modern cipher suite using only a small (<300 bytes), strictly bounded stack allocation?

As for those who do use the "heap", many can do so with a strict stack discipline, so it’s not really a heap. Reason being, the environment has an extremely low call stack budget, so all bigger allocations happens in another stack, located in the heap.

Then there are arenas, which are much simpler to implement than a general allocator, tend to have less overhead (both space and runtime) than malloc() or a GC, though I reckon they can still run out of space. Still, less treacherous than uncontrolled use of malloc().

Most of all though, even if the compiler can do mighty static region analysis to minimise heap usage and GC overhead, heck, even perhaps reduce it to absolutely zero in some cases, there’s still the problem of the programming interface: how does the programmer know about stuff like max heap usage? I guess the compiler could tell them, but then how do they fix it if usage exceeds the limit, or is unbounded? The wouldn’t be any clear link from heap usage to the source code, or it would be so diffuse it would be hard to determine where to begin.

And of course, a GC’ed program would likely rely on many more pointers under the hood than one that manages its memory manually, which means significant space overhead even if the GC implementation itself is perfect.

You want me to write something for an ESP-32, it’s gonna be in something like C, Rust, or Zig.

> many do not use dynamic allocation at all.

Exactly. You don’t need GC if you don’t produce garbage. And since Go uses stack-based memory like C you can avoid producing garbage just the same.

And if you do need some kind of special memory allocation then you can do the same tricks you do with C. You still don’t have to use the built-in allocator.

All the bellyaching about GC is silly because you don’t need it in the first place. The cries are like saying C isn’t suitable for microcontrollers because its standard library includes malloc… Just don’t use it.

> You want me to write something for an ESP-32, it’s gonna be in something like C, Rust, or Zig.

Sure. The Go creators were explicit that Go is designed for building network systems. That doesn’t mean it is impossible to use for anything else, but it was optimized for a particular niche. You will feel more comfortable using languages designed for microcontrollers when programming for microcontrollers.

But it’s not GC you aren’t using that gets in the way. You don’t have to use every language feature just because it is there. Rust doesn’t become unusable for programming because it has a string type and you only need integers. Just don’t use it.

Go already runs on microcontrollers, but _just like C_, care must be taken to avoid using features that are not compatible with microcontrollers (such as dynamic memory).
Most of those folks would never manage to replicate something like Xerox Cedar on their own, from 1980 in their beloved 2026 computers.

What are you talking about

> I believe everyone agrees that a "better C" has to have manual memory management.

This seems completely wrong. Manual memory management is perhaps C's most famous issue. I'm sure _someone_ thinks manual memory management is a feature rather than a bug, but I don't think there's any broad consensus about this at all.

Moreover, Go gives you a lot of levers to control your allocations, and with some care (probably less care than writing correct C code) you can avoid allocating at all (this is how Go's own runtime works).

> Manual memory management is perhaps C's most famous issue

C's most famous issue is that it doesn't have a dynamic memory management concept at all. You can implement manual memory management on top of C, like you can in any language, but it is not a core feature. If C had manual memory management then it could be reasoned about, which would avoid many of the pitfalls associated with memory management being bolted on top.

Whether that is a feature or a bug probably depends on what kind of software you are building. If you are targeting a PC, a dynamic memory management concept in the language can be useful. If you are targeting a small microcontroller where you don't want dynamic allocation then things can get a bit weird having language features that need to be disabled or having to rely on outside processes (code review, linters, etc.) to control use.

But the general consensus these days does seem to be that a language should include a dynamic memory management concept, even if it is sometimes disabled. So a "better C" likely would gain memory management, but you make a fair point that it doesn't have to be manual in nature.

> C's most famous issue is that it doesn't have a dynamic memory management concept at all. You can implement manual memory management on top of C, like you can in any language, but it is not a core feature. If C had manual memory management then it could be reasoned about, which would avoid many of the pitfalls associated with memory management being bolted on top

It seems like you’re conflating dynamic and manual memory management? Or maybe you are using these terms in a way I’m unfamiliar with. C definitely has manual memory management via malloc and free. It can be reasoned about, but doing so correctly is very difficult (this is what Rust’s ownership system formalizes, after all). I don’t think this is controversial?

> If you are targeting a small microcontroller where you don't want dynamic allocation then things can get a bit weird having language features that need to be disabled or having to rely on outside processes (code review, linters, etc.) to control use.

C has dynamic memory (again, malloc and free) and thus it is in the same boat as other languages that have to take care to avoid using dynamic memory when writing embedded code (a decade and a half ago I was an embedded engineer using C and C++).

> It seems like you’re conflating dynamic

Perhaps I should have said heap memory to be more clear, but the intent was to recognize that C does have memory management (the stack), but it manages it automatically. However, I don't think the intent was actually lost as you specifically called out malloc, which was directionally correct. So I guess there was no need to be more clear as the message was delivered just fine.

> C definitely has manual memory management via malloc

No. malloc isn't part of the C language, it is a library function and one that isn't guaranteed to be available at that.

> It can be reasoned about, but doing so correctly is very difficult

To the best of our knowledge it is impossible. That is why Rust's claim to fame is adding a model necessary to make reasoning about memory possible.

> C has dynamic memory (again, malloc and free)

Again, malloc and free are not language features. They are bolted on top. You can bolt malloc and free onto every language under the sun. You can even bolt malloc and free onto Rust, but you will then break the ability to reason about its memory if you use it.

malloc and free are part of the C standard library. I don’t see the point in picking nits between language features you don’t have to use and standard library functions that you don’t have to use.
> It can be reasoned about, but doing so correctly is very difficult […]. I don’t think this is controversial?

It’s actually much easier than 95% of programmers think. The trick is to stop using fine grained allocations all the time, which are very difficult without tricks like RAII or a borrow checker (not to mention the high runtime overhead), and start using arenas instead. https://www.rfleury.com/p/untangling-lifetimes-the-arena-all...

> I'm sure _someone_ thinks manual memory management is a feature rather than a bug,

I reckon mandatory manual memory management is not ideal. But the ability to manage some memory manually, including in cases that require something more flexible than a stack, is definitely a feature.

I believe every willing user of Zig, Odin, and Rust would agree with me on this one.

> […] with some care […] you can avoid allocating at all […]

As long as it’s crystal clear from the source code, or I have a tool that tells me which line of mine is responsible for the allocation.

> As long as it’s crystal clear from the source code, or I have a tool that tells me which line of mine is responsible for the allocation.

The tool is the escape analyzer, but I don’t think anyone has made it ergonomic yet.

Then it needs to be made standard. Without that, or an equivalent solution, garbage collected languages will remain unusable in constrained environments.
I'm not sure I agree (or maybe I misunderstand--I assume you're saying "the tool needs to be standard"?), but people use non-standard tools in C all the time. Every build tool or package management solution in C is non-standard. Every memory analyzer or static analysis solution is non-standard.

I think the real impediment isn't related to garbage collection, but it's just ecosystem inertia and industry knowledge inertia--the same forces that inhibit Rust adoption or C++ adoption or Ada adoption. Does that seem reasonable, or am I misunderstanding? What do you think?

> Of course, I’m pretty sure Go is much better than C at some problems. But there’s no way in hell it supersedes it.

C is great when you need to do manual memory management. Most software written today doesn't need to do manual memory management. (And frankly, for the typical software project, manual memory management is a liability.)

> Most software written today doesn't need to do manual memory management.

Need? Not really, not on the desktop (embedded is a different story entirely). But there are still benefits, as well as costs.

Thing is, most programmers have no idea what the actual costs an benefits are, because they have a hopelessly incomplete view of what manual memory management actually is. To them, and this includes long time C++ programmers, manual memory management means calling the general purpose allocator for every little object. At best they’d batch allocation in arrays or vectors, but otherwise it’s RAII for most stuff, and new/malloc() for anything more complicated. This kind of memory management have high costs and low benefits. In some cases the runtime overhead is even greater than using a GC.

And then there are arenas. Much easier to deal with, much lower overhead, much safer, more predictable… They make the cost/benefit analysis completely different, to the point you could ask yourself why you would even bother depending on a GC and the heavy runtime that goes with it.

https://www.dgtlgrove.com/p/untangling-lifetimes-the-arena-a...

> C is great when you need to do manual memory management.

Is it still, today? There are other manual memory languages today with better safety records and ergonomics. Plus a host of new and better features.

I think C is really great if you need C. For libraries, for knowledge, for size, for compatibility, for "simplicity", for fun, for whatever.

But other than having a strong reason to want C, on a purely language feature comparison, it's not great.

Compatibility is a big one. Probably the biggest reason to chose C over Zig or Rust today. But that can be remedied if the new language has a C compilation target.
Rust is memory safe, but not "manual memory management." IE, the vast, vast majority of Rust doesn't use pointers.
Go is not a better C, in the sense that you cannot write an OS with it. Runtime, GC, etc.

But Go is a better language for many programs that were often written in C: network servers, CLI utilities, TUI utilities, etc.

Plenty of OSes, since Xerox PARC days have been written in GC systems languages.

Interlisp-D, Smalltalk, Cedar, Topaz, Oberon, Active Oberon, Singularity, Midori, Ironclad

Go's runtime is written in Go.

The whole compiler toolchain, GC, compiler, linker, Assembler, is written in Go.

There are Go compilers for bare metal, no OS needed, like TinyGo, the runtime, written in Go is the OS.

I love the "you cannot write an OS in a GC language" discourse.

It isn't only a mainstream thing because everyone only cares about UNIX clones.

The fact that people care a lot about Unix clones is significant, though. nine_k could have been more effective in arguing the point, but it seems like a strong point to argue. Do you think you Go is flexible enough to write a Unix clone with performance equivalent to a C-unix? If so, why has it not been done?
Besides the sibling Biscuit, maybe because no one bothered to do it?

As simple as that, not everyone of us is a Linus.

I should also point out that if you are using a Mac, changes are its iBoot Safe C might already been replaced by Embedded Swift, a GC enabled systems language.

Chapter 5 of The Garbage Collection Handbook, or A Unified Theory of Garbage Collection paper for the incoming replies related to RC.

People care about UNIX clones because they are lazy, UNIX has the source code available, and an existing ecosystem that they don't want to replicate, so it always ends up being yet another clone, thus throwing away all the possible innovations.

We see this happening even with Haiku, Genode, Redox OS, or Windows now shipping alongside Linux on top of Hyper-V.

Unless one is an Apple or Google, with the money and will power to push something out the door, using Objective-C, Swift, Java, Kotlin, with plain C and C++ standard libraries, and even then people will bend backwards to put UNIX into those systems, even when the platform owners went to great effort to hide it under the official userspace APIs.

> As simple as that, not everyone of us is a Linus.

I’m pretty sure Linus could not have written Linux today. Too much hardware to support, too many drivers to write, before it’s remotely useful. Well, except perhaps on some specialised servers.

Yes, not everyone is Linus. Otherwise we’d be using GNU Hurd. But he also came at the right time.

It is no accident that recent attempts to write hobby OSes nowadays target type 1 hypervisors instead of real hardware.
> People care about UNIX clones because they are lazy, UNIX has the source code available, and an existing ecosystem that they don't want to replicate, so it always ends up being yet another clone, thus throwing away all the possible innovations.

Which is a real shame, because people thinking outside the UNIX clone box have come up with some incredibly innovative operating systems like Plan 9 and OS/400.

There is Biscuit from OSDI 2018 https://pdos.csail.mit.edu/projects/biscuit.html
> If so, why has it not been done?

Because of the 30 million lines problem. Writing a serious OS kernel nowadays is flat out impossible: the hardware is just too diverse, it requires too many drivers. The best we can do right now is just add to an existing kernel. Kind of a vicious cycle: the more drivers we accrete to any given OS, the more impossible it is to get feature parity from the ground up.

The only solution to that is for hardware vendor to standardise their interfaces (at the hardware level), and actually give us the fucking manual. And I mean the real manual, the one that tells us voltages and pins and baud rates and the actual wire formats required to talk to it. A proprietary Windows driver is not a manual, even if it comes with an SDK. Heck I’m not even sure the source code of a Linux driver would count.

But they won’t do it. They just won’t. So unless we do something drastic like forbidding hardware vendor to ever ship software (to force them to standardise and document their actual interfaces), writing another OS that can actually compete with the existing ones will remain flat out impossible.

> Go's runtime is written in Go. The whole compiler toolchain, GC, compiler, linker, Assembler, is written in Go.

There's some nuance to this. The runtime code uses specific compiler support and restrictions that are not ordinary Go - possible (or even done, like this case) ≠ ergonomic. No doubt of course that for the Go project, this is still a win.

Do you think libc is also written in pure ISO C?

I love how language extensions, and subsets are always valid for C and C++, but used as an attack against other languages, as if to prove they are not good enough for a specific use case.

Well, neither are C and C++, as they either have to rely on hand written Assembly code, language extensions, or be gutted down into subsets outside the official language standard.

> but used as an attack against other languages, as if to prove they are not good enough for a specific use case.

Ultimately, it comes down to the (subjective, sure) ergonomic limitations of a language. To me, Go without garbage collection and implemented using specific compiler support and significant amount of unsafe operations, does not really represent Go, just as, so to speak, a Rust project containing 90% unsafe code does not really represent Rust. Both are possible of course, and there are even use cases in which they may be desirable in the broader context (ie. the Go runtime).

Therefore, this is not an attack on the language; it is simply an assessment of its ergonomics for particular use cases.

> Go is not a better C, in the sense that you cannot write an OS with it

Can you even write an OS in pure ISO standard C? I know atomic (https://en.cppreference.com/c/language/atomic) was added in C11, but is that sufficient to write an OS? How do you write a kernel-user space transition in standard C, for example?

The larger runtime makes bootstrapping go for unsupported architectures more laborious than C, but it's not a hard blocker. The function call overhead for inline assembly feels like more of an issue doing close to hw programming. It can be avoided for the runtime, but user go code can't escape it afaik.
Just like when C was being brought to life in PDP hardware and there was nothing else, someone has to put the work into it.
Yeah but they were intentionally trying to build a better C++/Java, not a better C. It wasn't even aimed at things that C is good at. It was mostly aimed at writing high performance servers that have to scale really big not only in terms of performance but software complexity, size and contributions. So Go is a better C++ or Java for writing servers according to its creators.
A better C++ is by definition a better C, no one should be using C in 21st century beyond UNIX clones, and embedded devs that are religiously against C++, even all modern C compilers are written in C++ nowadays.

Anything you can think C is better, it isn't ISO C, rather non standard C compiler specific extensions, which can language can also be.

Just do like in K&R C days, use Assembly for what language isn't directly capable of, and it is right there as part of a regular Go toolchain installation.

> A better C++ is by definition a better C

The definition is wrong, then.

I wrote C++ for most of my career. And as of late, I found myself avoiding more and more features from it. The STL is mostly trash, not worth the increase in compilation times. Templates are good for containers, but that’s about it. Inheritance and polymorphism are circumstantial enough that I’m not sure they’re worth adding to the language: in the rare cases I do need them, I can always write my v-table by hand.

The more I write C++, the more I find C is not that far from that "much smaller and cleaner language struggling to get out". And I say that fully aware of the many egregious faults still present in C.

There are two features from C++ I would really miss in a big C project: generics (templates), and destructors. But then we can always write a lightweight pre-processor to add those generics and a `defer` statement. Even if it requires a full parser, C parsers are pretty easy to write.

I've been toying with a better C... https://github.com/panaflexx/classyc

Started with the excellent MIR compiler, and I wrote much of the class/string/json/dict, exceptions and AI made the generics,ownership tracking, and safety checks/traps. Added some go

The memory model is mixed, I ended up using arenas for dictionaries and adding a full ownership checking / safety checking compiler stage.

It's purely for the joy of making something interesting.

Oh wow, interesting project... I've also been working on a "better c" for a few years, and while it was originally a JIT language (using jitasm), I recently switch to MIR, but since I already had my own lexer/parser/etc stack, I ended up changing it to basically lower everything into a node_t compatible AST tree and handing that off directly to c2mir, bypassing its parser.

Maybe you're interested in collaborating? :)

https://github.com/derekbsnider/madc

I see it differently, given how C with Classes came to be, a Typescript for C in 1989.
You know, I’m actually tempted right now to do my own "C with <my-features>". Except I wouldn’t make the same choices as Stroustrup did. For one, I wouldn’t aim for popularity. I’d want something that works for me, and I’d consider it a success even if I’m the sole user. Source compatibility would not be a goal, and I’d most probably skip classes entirely.

The way C++ was first implemented was a good idea. It’s everything else I disagree with.

The origin of a thing does not forever define what that thing is and always will be. C++ has evolved far beyond C with Classes.
> Yeah but they were so that intentionally trying to build a better C++/Java

They were trying to build a faster Python, thinking that would appeal to C++ developers. The theory was that developers were using C++ at Google because they had to, not because they wanted to, and would choose something like Python instead if it were up to the performance task. Although it turned out in the end that C++ developers actually wanted to use C++.

No they weren't, in fact they were very surprised by the adoption from Python folks.

"I was asked a few weeks ago, "What was the biggest surprise you encountered rolling out Go?" I knew the answer instantly: Although we expected C++ programmers to see Go as an alternative, instead most Go programmers come from languages like Python and Ruby. Very few come from C++.

We—Ken, Robert and myself—were C++ programmers when we designed a new language to solve the problems that we thought needed to be solved for the kind of software we wrote. It seems almost paradoxical that other C++ programmers don't seem to care."

https://commandcenter.blogspot.com/2012/06/less-is-exponenti...

> No they weren't, in fact they were very surprised by the adoption from Python folks

I'm kind of surprised that the Go creators were surprised by this.

Like I know they didn't like C++, but Go is by no means a replacement for C++ despite whatever was said about "building a better C++/Java".

It took so long to pick up generics that Rust was already around and the logical next step for C++ developers by the time that happened. And that's only scratching the surface of why people choose C++.

On the other hand, having a more performant "simple" language that directly supports concurrency and lets you compile to native code without a ton of ceremony could be very helpful indeed if you're a Python dev who need to take an app to the next level without having to learn intricacies of C FFI or the GIL.

Why wouldn't they be surprised? Python users were already using Python, so presumably it already solved their problems and they wouldn't benefit from another Python. Through Google's eyes, anyone who needed performance would have already long abandoned Python for C++.

What Googlers don't have a grasp of is how resource constrained most other businesses are. They cannot afford to hire C++ experts and were using Python because Python developers were the only developers they could get their hands on, even where Python wasn't providing sufficient performance.

> No they weren't

Indeed they were. Your quote even says so — seeking out to solve the problems C++ was thought to have, which is that it didn't have the ergonomics of languages like Python, as explained in the original public Go announcement. To "feel like a dynamically-typed language" was a primary motivation. That was clearly told.

You can certainly hold the view that a better C++ and a fast Python are one and the same, but I suspect the HN crowd will vehemently disagree with you. Conflating the two would not serve to communicate much here.

> were very surprised by the adoption from Python folks.

Quite naturally. Python users were already using Python. One would be inclined to think that they didn't need another Python. Except it turns out they did, because it was actually they who needed a faster Python.

> Although it turned out in the end that C++ developers actually wanted to use C++.

Oof, I don't think so. It's just ridiculous how many things Google uses C++ for where something like Go or Java would be a better fit, and everyone I talked to agreed. No one liked C++ for search-like things.

I think they just underestimated how powerful the network effects were. People wanted to use Go, but then it didn't have Flume support forever, so what can you actually do with it? And all the NLP libraries were in C++. And the vector-search library was C++, and graph clustering, and so on. One of the basic data formats was super-clunky for ages as well, but I forget if it was SSTable / RecordIO / Capacitor or what.

Or perhaps the Go creators were just writing very different code in very different domains from what I saw as the bread-and-butter C++/flume data-crunching pipelines of Search and Maps.

> Although it turned out in the end that C++ developers actually wanted to use C++.

Survivorship bias. You're only looking at the people who remained C++ developers. A bit like a politician bragging that they have 100% approval rating among their supporters even as their supporter count dwindles.

Google kept on building C++ services despite Go being available and fully backed by the company. Developers could have used Go if they wanted to, but they didn't.

There are obviously exceptions. There always are. But the reality is that Go was picked up by the Python crowd instead. Which was noted as being surprising at the time as it was assumed that Python users didn't need another Python. What was hard for the Googlers to fathom was that anyone would use Python in a performance-sensitive area to begin with.

I disagree. Go's main plus point is concurrency, whereas the irony is it lacks thread safety—something languages such as Java have had from, let's say, day one. You can fix that issue by manually managing sync.Pool objects for performance and, on top of that, use channels, but this completely defeats the purpose of Go's simplicity. Even if you want a top-of-the-line performance with some convenience, there exists an open-source framework called Netty. It is an absolute beast for network I/O, beating out Rust in some cases and sitting just behind C. And it's all in Java.
My point was not about Go vs Java.
You missed a bit of my point. The point am trying to convey is that what it was made for that is efficiency and networking it fails somehow and somewhere in both. As i was surprised when i came across the thread safety issue of go which for me was diabolical, because go was meant to be lean at least on threads while maintaining thread safety by default.

The next point would be gc,maybe it can be improved and its being improved the recent being mark sweep algorithm was made a bit efficient by addition of more meta data about the total object structure, not going into it more.

Thing is it breaks the fundamental promise it was suppose to keep and yes its better than rust for network applications(am talking of getting things done in default way). It's stdlib is the strongest among all languages.

That summarises my point go needs severely improve it's issue of thread safety by default provision and gc efficiency.

Wikipedia says Alef lacked garbage collection, contributing to it being abandoned.

But does Limbo have any 'fatal flaws' like that? Especially compared to Go.

No doubt Rob Pike is a prolific and capable programmer. But seems like he needed quite a few attempts before arriving at Go (looking at C's flaws -> Alef -> Limbo -> Go, Wikipedia also mentions a domain-specific language called Sawzall).

Which is another proof, that the professional experience of the UNIX idols, has shaped why Go, and not C all the way.
ZIG is already the better C.
Zig is what Modula-2 already offered in 1978, Ada in 1983, Object Pascal in 1986, revamped in curly bracket syntax.

We need a little more in 2026, besides compile time execution.

Aside from a GC, what other features are "more 2026"? I'm curious.
I really wish it had more industry buy-in, in terms of funding and community participation. The language has so much to love about it, and even the typical criticisms - some deserved, some not - can be improved or solved over time because it has solid design and foundation. Like most well-built systems, there's a single person (or few people) with coherent vision driving the development, in contrast to C++ and other "design by committee" style projects.