> Now, if someone combined a pauseless GC with proper cleanup (i.e. skipping GC altogether) of variables where the compiler could determine when they can be thrown away, matters would be different. (So, in other words, the compiler inserts `malloc` (or whatever) calls, and ensures that every variable created is either `free`d exactly once after it becomes unreachable or is added to the set tracked by the GC (or is a constant - especially pertinent with strings). With (hidden) local variables to track control flow when different branches cause different allocations.)
Freeing is not an issue, you don't pay for garbage in a proper GC, as the GC only scans living objects, and never frees.
The problem is allocation, as a scan is triggered when a certain number of bytes has been allocated. Go allows you to skip allocating on the heap (thus the GC), since you can define what is allocated on the stack or heap.
You potentially could do as you propose, by inserting "free" at certain points where you could prove the variable was safe to throw away. Free would basically just then say "you can now postpone allocation, you have more free memory". But this has it's own drawbacks. For one you a minimal amount more to do (calling 'free'), but more importantly you will increase time spent in allocation objects, because you have to scan the heap for available space instead of just bumping a pointer.
The most important thing is allowing the programmer a way to avoid the managed heap, which Go does, and C# to some degree does through structs.