That observation checks out. About a decade ago, one of my former coworkers areas of interests was making Java applications and frameworks that didn't use the any additional heap beyond app initialization. This was apparently for embedded/low power devices. It was neat to hear about as a C++ dev, but I'm not sure how much of a business or technical advantage it had beyond that.
I think that people hyperoptimize for no reason though. How many applications are there that require no GC? I feel like you can write the no-gc code in Rust and then just use it from the C ABI from any GC language.
Embedded systems benefit substantially from decreased GC, especially in the context of non-relocating collectors (as with Go) in machines without memory management units. The problem is not the overhead, it is the effect of memory fragmentation.
The OP article here is nice because it allows selective enforcement of allocation free code. If you have code that has some discipline about heap allocation such as having all small allocations then you are OK on the fragmentation front.
I've been writing Rust for a year and change professionally, also as a hobby, and even though I love it, it's really annoying to choose the allocation model and lifetime model at every step.
For example,
- A field in a struct cannot borrow another field so now you'd have to create two structs. I guess you could write an impl to borrow that field instead of storing the borrow.
- In simulation software I wrote I found that mimalloc does just as good as bumpalo (within 5%) so carrying a bump everywhere was useless. Not really a rust issue but rather a loss of faith in manual memory management for me.
- Eventually over time you start adding Arc<Fn(Blah)->Foo> everywhere and Box, Arc and friends start coming in. But you can't have cyclical references there so you have to use Weak. And you have to use OnceCell or LazyCell or LazyLock for things that should run at start time.
Rust is really great for the crates, but ...
For applications which are not absolutely performance critical and cannot* be stated pretty easily in linear logic + clone + drop (such as audio streaming) you can get away with Haskell
Coming from an environment in which we extensively use mixed codebases (C++ and Python) to combine performance and ease of prototyping, one of the pain points is that there is almost always some impedance mismatch between the lifetime/ownership models of the two languages. When something goes wrong here, it's usually a real pain to debug and resolve, and often the resolution involves imposing constraints from one language onto the other (e.g. C++ that would idiomatically use unique_ptr instead needs to use shared_ptr because unique_ptr is essentially impossible to completely capture in Python).
Given the issues involved in mixing languages, there can be cases where sticking with one "good enough" GC'd language and having to write a few performance-sensitive parts in very unidiomatic form to minimize/eliminate GC work is preferable.
I feel like that may be more of an issue with how Python's C API is designed, leaking basically most of its internals, and then having to remember to manage (increment/decrement) Python objects correctly.
The end state of inappropriate use of garbage collected languages.
Go/GC are mostly fine for medium frequency medium scale mid stack problems.
It’s bad for high frequency computing. It’s bad for very small targets, for very large targets, and by association software that lives at the very bottom of the stack.
Go is a high efficiency framework for writing mid sized servers, not a systems language. The runtimes hard opinions and closed interface are the reason for this enclosing definition and the GC is one part of that.
Similar with malloc in C / new in C++, especially for short-living, repeating, in hot path. And then you hear: avoid allocations, use constant-size buffers or memory pools. Avoid spawning threads, use thread pools.
Maybe. Go code is designed to be readable and stable. My guess is that most Go code is running fine with garbage collection. There are always edge cases and then just choose another language.
Hey folks, this is my project. I really liked OxCaml's zero_alloc feature [0], something you don't get in Rust or Zig or Go*, and have been trying to get a handle on allocations in The Consensus's code search engine.
It's an extremely early project but it's been helping me out a bunch already. Would love to hear your bug reports or requests if you try running it on your codebase.
If you don't allocate, why do you even need Go or Rust? LLMs handle less elegant syntax just as well. I use a zero-malloc C dialect with slices and bounds-checking.
Just recently I was sketching a smallish database in zero-malloc C. https://replicated.live/blog/db That was partly because most LSM data structures are read-only, so mmap was good enough and concurrent access was safe for the bulk of the data.
The "hot zone" (memtable) then requires some intricate engineering, but that is the case either way.
I agree this isn't the absolute ideal form. I'd rather it be built in as a language directive or something. noalloc does parse compiler escapes emitted by the Go compiler with `-gcflags=-m -S`. One problem is that these are not really a standard format. But I hope that with testing across versions (over time anyway, I only run this on Go 1.27) it'll be stable enough.
Tides have shifted somewhat, but I suspect this may still be rejected.
Unfortunately a lot of people’s experience with such proposals was also historically getting gaslit by a brigade, so a lot of folks moved on and won’t bother trying again.
I think we can learn something here.
The OP article here is nice because it allows selective enforcement of allocation free code. If you have code that has some discipline about heap allocation such as having all small allocations then you are OK on the fragmentation front.
After a few rounds of this though it might end up seeming like it's not worth the hassle and it's easier to build the whole application in Rust.
For example,
- A field in a struct cannot borrow another field so now you'd have to create two structs. I guess you could write an impl to borrow that field instead of storing the borrow.
- In simulation software I wrote I found that mimalloc does just as good as bumpalo (within 5%) so carrying a bump everywhere was useless. Not really a rust issue but rather a loss of faith in manual memory management for me.
- Eventually over time you start adding Arc<Fn(Blah)->Foo> everywhere and Box, Arc and friends start coming in. But you can't have cyclical references there so you have to use Weak. And you have to use OnceCell or LazyCell or LazyLock for things that should run at start time.
Rust is really great for the crates, but ...
For applications which are not absolutely performance critical and cannot* be stated pretty easily in linear logic + clone + drop (such as audio streaming) you can get away with Haskell
I don't see much point in choosing a language just to bypass it's features.
Given the issues involved in mixing languages, there can be cases where sticking with one "good enough" GC'd language and having to write a few performance-sensitive parts in very unidiomatic form to minimize/eliminate GC work is preferable.
Go/GC are mostly fine for medium frequency medium scale mid stack problems.
It’s bad for high frequency computing. It’s bad for very small targets, for very large targets, and by association software that lives at the very bottom of the stack.
Go is a high efficiency framework for writing mid sized servers, not a systems language. The runtimes hard opinions and closed interface are the reason for this enclosing definition and the GC is one part of that.
It's an extremely early project but it's been helping me out a bunch already. Would love to hear your bug reports or requests if you try running it on your codebase.
[0] https://theconsensus.dev/p/2026/06/27/the-feature-in-oxcaml-...
* I think you can get this in Clang and maybe GCC with `__attribute__((nonallocating))` though.
Just recently I was sketching a smallish database in zero-malloc C. https://replicated.live/blog/db That was partly because most LSM data structures are read-only, so mmap was good enough and concurrent access was safe for the bulk of the data.
The "hot zone" (memtable) then requires some intricate engineering, but that is the case either way.
You can go quite far with zero-malloc C.
Tides have shifted somewhat, but I suspect this may still be rejected.
Unfortunately a lot of people’s experience with such proposals was also historically getting gaslit by a brigade, so a lot of folks moved on and won’t bother trying again.
That might be changing (slightly) because of the Tamago work on NOOS.