Garbage collection deals with removing allocated objects that are no longer used. Non-managed languages without a garbage-collected runtime will have to issue the corresponding “free” command for each heap-allocated memory at the necessary place. This is a hard thing to do, because freeing it before the time can result in use-after-frees that are huge vulnerabilities, and not freeing it is a memory leak.
Reference counting is a form of GC, but in itself it is not sufficient (it will leak circular references as those will always reference each other). Due to its simplicity, in a sufficiently expressive language one can create this abstraction (see shared_ptr in c++, RC, ARC in Rust) and with the necessary caveats of not using circular structures with it, you get an ergonomic semi-GC without runtime support.
Compile time/runtime issue is slightly different - in case of many allocations one can know where you will have to free it even at compile time. C++ has a concept called RAII, which is basically “put a free at the end of the scope”. This works surprisingly well for most use cases and Rust sort of mandates this semantics by the compiler in the general case.
But an object’s lifetime can’t be known in advance (think of a User object for a logged in user. Is it still needed or had it expired?) in the general case, that’s why runtime GC is needed like refcounting or tracing GC in managed languages.
These give me some good context to go off of. Are there any resources you’d recommend on an intro to garbage collection for devs without the low level cs background? I’ll read a textbook if I have to, I just don’t know where to start with that sort of thing in terms of biting off comprehend-able amounts of info
Unfortunately, I am not aware of too much intro-level texts on the topic (I would be happy to get some from other commenters). One well-regarded book is Crafting Interpreters, though it covers a wider topic. Didn’t check this section, but perhaps its GC chapter is a good introduction: https://craftinginterpreters.com/garbage-collection.html
Reference counting is a form of GC, but in itself it is not sufficient (it will leak circular references as those will always reference each other). Due to its simplicity, in a sufficiently expressive language one can create this abstraction (see shared_ptr in c++, RC, ARC in Rust) and with the necessary caveats of not using circular structures with it, you get an ergonomic semi-GC without runtime support.
Compile time/runtime issue is slightly different - in case of many allocations one can know where you will have to free it even at compile time. C++ has a concept called RAII, which is basically “put a free at the end of the scope”. This works surprisingly well for most use cases and Rust sort of mandates this semantics by the compiler in the general case.
But an object’s lifetime can’t be known in advance (think of a User object for a logged in user. Is it still needed or had it expired?) in the general case, that’s why runtime GC is needed like refcounting or tracing GC in managed languages.