Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The problem with garbage collection is that it doesn't work for other kinds of resources than memory, so basically every garbage collected runtime ends up with an awkward and kinda-broken version of RAII anyway (Closeable, defer, using/try-with-resources, context managers, etc).

Static lifetimes are also a large part of the rest of Rust's safety features (like statically enforced thread-safety).

A usable Rust-without-lifetimes would end up looking a lot more like Haskell than Go.



> basically every garbage collected runtime ends up with an awkward and kinda-broken version of RAII anyway (Closeable, defer, using/try-with-resources, context managers, etc).

RAII works only for the simplest case: when your cleanup takes no parameters, when the cleanup doesn't perform async operations, etc. Rust has RAII but it's unusable in async because the drop method isn't itself async (and thus may block the whole thread if it does I/O)


There are workarounds. You could, for example, have a drop implementation spawn a task to do the I/O and exit.

Also, if your cleanup takes parameters, you can just store them in the struct.


In my experience async drop is a nice to have, not a must. Futures::block_on is good enough for the happy path in low stakes scenarios.

When dealing with async operations they tend to end up at a network boundary and thus the service enters distributed system land.

Now the async drop also has to handle the server crashing before the drop happens and at any time when it happens. Keeping that in mind trying to actually drop something becomes quite meaningless since you need to handle all other cases either way.


I agree but does RAII necessarily imply parameter-free destruction?

Personally I love Rust’s `fn foo(self, …)`, which is just like a regular method but consumes the value.

Deallocate by default is fine, but sometimes you need to run specific destructors (linear type style). I’ve long wished for an opt-out from implicit drop semantics for resource/handle types.


You can (kind of) emulate linear types by making `drop()` unlinkable[0]. Of course, I wouldn't recommend doing this since the error messages are awful and give no explanation at all of where the actual problem is...

[0]: https://play.rust-lang.org/?version=stable&mode=debug&editio...


Haha never seen that one before! Unfortunately it’s pretty radioactive, couldn’t even box that thing.


People miss that there is a quantitative aspect to these things as well as a qualitative aspect. That is, many programs allocate millions of pieces of memory a second and for a wide range of different purposes whereas there might be a limited number of other resources of an even more limited set of types. Any change in the code probably has some affect on memory allocation, but many changes won't have any effect on allocation of higher-level resources.

Thus the complexity of handling memory is greater than that of other resources and the consequences of getting it not 100% right are frequently worse.


I quite like context managers and try-with-resources style constructs. They make lifetimes explicit in the code in a fairly intuitive way. You can get yourself turned around if you deeply nest them, etc but there are usually ways to avoid those traps.


You make an interesting point. Has any language introduced a generic-resource-collector? You're not supposed to use deconstructors to clean up resources because you're left to the whims of the GC which is only concerned about memory.

Has anyone build a collector that tracks multiple types of resources an object might consume? It seems possible.


Erlang is probably the closest. The word you want to search for is "port". If it doesn't seem like it at first, keep reading. It's a very idiosyncratic take on the topic of you view it from this perspective because it isn't exactly their focus. But it does have a mechanism for collecting files, sockets, open pipes to other programs, and a number of other things. Not fully generic, though.


Python handles all kinds of stuff with garbage collection.

The problem is that things like closing a socket are not just generic resources, a lot of the time nonmemory stuff has to be closed at a certain point in the program, for correctness, and you can't just let GC get to it whenever.


I don’t think this is true. Context managers call special magic “dunder” methods on the instance (I don’t remember the specific ones), and I’m pretty sure those don’t get called during regular garbage collection of those instances. It’s been a few years since I was regularly writing python, so I might be wrong, but I don’t believe that context manager friendly instances are the same as Rust’s Drop trait, and I don’t think their cleanup code gets called during GC.


Python is a fun case of "all of the above" (or rather, a layering of styles once it turns out a previous one isn't workable).

Originally, they used pure reference counting GC, with finalizers used to clean up when freed. This was "fine", since RC is deterministic. Everything is freed when the last reference is deleted, nice and simple.

But reference counting can't detect reference cycles, so eventually they added a secondary tracing garbage collector to handle them. But tracing GC isn't deterministic anymore, so this also meant a shift to manual resource management.

That turned out to be embarrassing enough that context managers were eventually introduced to paper over it. But all four mechanisms still exist and "work" in the language today.


Are you saying that a finalizer is guaranteed to run when the last reference is deleted? So you could actually rely on them to handle the resources, as long as you are careful not to use reference cycles?


In CPython 2.7, yes. In CPython in general, I believe it's currently still the case, but I don't think it's guaranteed for future versions.

For Python in general, no. For example, as far as I know Jython reuses the JVM's GC (and its unreliable finalizers with it).

It's also easy to introduce accidental cycles. For one, a traceback includes a reference to every frame on the call stack, so storing that somewhere on the stack would create an unintentional cycle!


The tracebacks were a lot of what made me cut back on using weakrefs and trying to make things manage their resources automatically.

Now I use close() methods for anything that needs to be closed. If I mess up and there's some obscure bug, hopefully GC will fix it, but it seems too brittle and easy to make mistakes with to rely on.


Wrote Python professionally for years and didn’t know all of this. Thanks!


with: does in fact use different dunder methods, but __del__ allows one to do GC-based cleanup if one wishes.


Don't static lifetimes just mean that leaking memory is considered 'safe' in rust?


Static lifetimes as in "known and verified at compile-time", not "the 'static lifetime".


> The problem with garbage collection is that it doesn't work for other kinds of resources than memory

Why is that a "problem with GC"?

Abstracting away >90% of resource management (i.e. local memory) is a significant benefit.

It's like saying the "problem with timesharing OS" is that it doesn't address 100% of concurrency/parallelism needs.


I think a charitable reading would be that by "problem" they meant "limitation".




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: