Food for thought: GC-less Go build mode

I’ll go straight to the point - I was wondering if it would be possible to have a build mode that would remove GC and runtime and switch to manual memory management?

My vision is something similar to Odin, which passes a context object to every function call as last, hidden, argument, which contains allocators(standard and temp, with option to overwrite them) and other useful properties(logging…). So every make/append/new autmatically receives it as well, all the way down from the main(), unless overwritten for local memory arena use case and things like that.

Nothing has to change for the users except they now have to manually free allocated memory in their code, just like Odin does. Both support defer and Gophers are used to deferring calls already. A new “free” keyword would be needed and “delete” would have to change how it works in this mode. It’s very simple and effective, imo. Odin also has a debug allocator that will catch allocations that have not been freed, which can help with transition to this new mode.

I’m don’t do low-level/systems programming, but I fail to see how this would be too hard to add as a feature, as it would essentially just disable the native concurrency(goroutines and channels) and would just require better support for manual thread management so people can manually re-implement similar logic for concurrency. all of which can be achieved with build tags.

I think this could open up Go to a whole new world of usability, markets and domains - while preserving what we all like about Go, just with the performance turned to 11.

I’m aware that there is a keyword conflict with “context”, so that’s up for discussion and final implementation, so don’t get hang up on it as it is just a miniscule aspect of all of this.

Thoughts?

I think this would break the abstraction layer on which most go code runs. Go is designed from the ground up to be a type and memory safe language with some escape hatches for special use-cases. There already are ways to disable garbage collection completely (by setting the GC parameters so the GC effectively never starts a cleanup) - this is a good option for some use cases like short lived programs (one off CLI tools) or carefully planned software with minimal allocations.

Other users reduce allocations und thus GC by using techniques like stack-only functions or pooling to only allocate once and re-use memory. One could even create a class which allocates a big chunk of memory up front (like a big byte-array) and then just allocate your logical objects in this space manually.

Go is an opinionated language and provides sensible defaults which fit for the vast majority of code bases in the target audience. And the escape hatches (unsafe package…) provide enough options to increase performance in hot spots or special use cases. I find it quite hard to imagine a scenario in which your proposed solution would actually be preferable overall.

2 Likes

There already was an experiment with manual memory management with arenas. It didn’t work as intended thus, was put on indefinite hold. IMHO, let golang be the gc-ed language as it is right now. It was never designed to be a language with manual memory management in the first place. There are tons of other solutions people can use if they want to handle their allocations on their own.

1 Like

A GC-less build mode isn’t just removing the collector—it breaks several core invariants of Go. Goroutine stack growth, pointer movement during stack copying, escape analysis decisions, and even map/slice safety rely on the GC knowing what memory is live. Without that, you’d need explicit ownership rules, lifetime annotations, or region-based memory, which Go intentionally avoids. At that point, the complexity shifts from the runtime to the developer, and the language semantics stop being Go in any meaningful sense.

3 Likes