Hacker News new | past | comments | ask | show | jobs | submit login

I am by no means a Golang developer, so don't construe my OP to be a particular endorsement of Go. But after 25 years writing C/C++, Java, Javascript, and working on language implementations, garbage collectors, JITs, VMs--what most people consider "system software"--I feel strongly that requiring programmers to reason about ownership for memory management is a complete waste of everyone's time. It's more mental load than programmers should not be burdened with, no matter how good the tools for it are. The performance costs of GC are routinely overstated by C/C++/Rust advocates to the point that GC implementation experts have pretty much become cut off from the conversation. No one really measures properly, anyway.

My honest opinion is that it is 2021 and it's about time we let computers manage memory already.

Rust can have their ownership for race conditions if they want, but I am so tired of being gaslighted about how important it is that I be hounded to track every solitary byte of memory when I am busy thinking about more important things.




> I feel strongly that requiring programmers to reason about ownership for memory management is a complete waste of everyone's time. It's more mental load than programmers should not be burdened with, no matter how good the tools for it are.

On the contrary, ownership tracking is important for general code correctness, not just memory management. It's practically impossible to audit and assess correctness of a program with pervasive confusion in the overall patterns of what code owns what data. Memory safety is also important of course, but it's mere table stakes.


I agree, ownership is an important idea on its own. So I wish more mainstream gc languages would expose some kind of optional ownership idea. Such as ability to "give" a collection to another collection contructor, which then would not have to do a boilerplatey/inefficient "safe-copy" in the contructor. Maybe that opens a can of worms and can't be easily retrofitted into existing languages, I don't know, but I'd like to see it tried.


This is why this subject is hard to talk about. I fully agree that humans shouldn't have to care about memory management! That's why Rust is so important: the compiler thinks about it so that I don't have to. People can even agree on the main points and completely disagree on the conclusions.


Unfortunately the space is littered with confounding variables. For example, just measuring the impact of GC overhead is bound to be tricky, because a trashy program (one that allocates a lot of garbage) is going to make GC look bad in comparison to a program that is careful to reuse data structures. And that's mostly because of the second-order effects of the memory hierarchy, and not so much about the raw cost of allocation or deallocation! A lot of language library choices contribute to trashiness; e.g. in Java it is very difficult to avoid making lots garbage if you use essentially any of the JDK. You can't even parse integers or strings effectively without multiple copies. I really don't know how Go's libraries look in that regard, but the truism generally holds that the more bullet-proof you try to make APIs, the more defensive copies you end up making.

Is the antidote to those inefficiencies an ownership model that forces you to reason about mutability? I don't know. I kind of think no, primarily that it is a performance consideration that infects the API and spreads everywhere; it's a distraction. Is it instead to have all immutable data structures and an insanely smart compiler that replaces copying with in-place mutation, so that pure functional languages compete with highly tuned, pervasively mutable code? I kind of also think, no. And primarily that's because performance cliffs get worse and harder to predict, the smarter your compiler is.

The mutability/ownership question is confounded with allocation and deallocation. The latter really should never be on any programmer's mind, IMHO. In Rust, it seems there isn't much support for decoupling the two, e.g. by having an automatic garbage collector. That's also an unfortunate reality forced on language implementers by the fact that LLVM has steadfastly refused to support stackmaps as a first class concept for more than 15 years. Many, many projects have died because of LLVM's stackmap support being lacking or broken. That's unfortunate because precise stackmaps are a key enabler for many GC techniques, and without them, you end up with some form of conservatism that make certain optimizations impossible, and forces a particular design for nurseries.


Yeah, you have to get into all of these details to have a productive conversation. Likewise, I don't actually think a lot of this inherently has to do with performance, basically, I could make a similarly lengthy comment about how

> I kind of think no, primarily that it is a performance consideration that infects the API and spreads everywhere; it's a distraction.

is something I fundamentally disagree with; unfortunately, I don't really have the time at this moment to even get it into the level that you have here, but it's a good conversation to have, and a needed level of details to even have a good conversation.


Seconding the disagreement with the idea that mutability tracking is a distraction. I would give anything to have true deep-immutability enforced by TypeScript, but unfortunately the language semantics make it virtually impossible. In Rust, it might be my favorite feature.


Interesting. We definitely should have this conversation in the proper detail, because my estimates of Rust's language design priorities must be off.


>For example, just measuring the impact of GC overhead is bound to be tricky, because a trashy program (one that allocates a lot of garbage) is going to make GC look bad in comparison to a program that is careful to reuse data structures.

This is completely irrelevant because stop the world pauses are global and affect your entire application no matter how careful you write your code. Every library you use has to be carefully written, every single line of code you write has to be carefully written regardless of whether it is time critical or not, this is a complete dead weight for the entire application.

With isolated GC heaps your time critical code will be isolated from non critical code. In practice the easiest way to do this is to just launch a separate application. This hurts a lot with the JVM because it wants to own your entire server (mostly RAM) for some inexplicable reason and you might as well write that part in C++ if you do inter process communication anyway.


Thing is, in order to have pervasive reuse of data structures (mitigating the overhead of GC) while maintaining safety and correctness, you have to track uniqueness and mutability throughout the program. At that point, you've got something that's practically indistinguishable from borrowck.

The real use case for GC is managing data where there's no well-defined "ownership" pattern, such as when dealing with general graphs (e.g. in a symbolic computing or GOFAI application). That's a remarkably niche domain.


I don't write software at fang scale and I've been bitten by GC pauses in other languages. Even when it doesn't cause issues, you can see it in the response times charts.

Not having to worry about that is very nice. Using less memory, I agree it's not terribly important but it is a nice bonus - which comes in handy when working on embedded, videogames or in constrained conditions (like when you can put a lower limit to your containers and run more containers on the same machine).


My main objection to this take is that ownership is about far more than just memory management.

Garbage collection lets you avoid memory leaks, double-free, use-after-free, and that's it. In Go, it's up to you to correctly handle every other type of resource correctly, as the compiler won't offer you any help.

An ownership system like Rust's is a general-purpose tool that handles all kinds of resource management. File descriptors, database transactions, session types, locks, etc.

People praise Go's channels, but Go channels are way more complicated to use correctly, and part of that is due to ambient ownership (the other part is due to bad error handling). You need to manually keep separate track of how many outstanding writers there are are, and then explicitly close the channel. In Rust, the channel is closed when all senders or all receivers are dropped. I don't have to do anything, and it's always as correct as what I'd have to implement and maintain myself in Go.

Another great example is how Rust's mutex can own the guarded value. In most languages, a mutex is a separate object, and it's up to you to be careful to make sure you never access it without acquiring the mutex first, to always release after the last use of the guarded value, and to never keep a reference to the guarded value after releasing the mutex.

With Rust, the ownership system is used to handle and verify all of that, so I do less work, have less to think about it, and can't make those mistakes. The Mutex owns the value. Mutex::lock() and ::try_lock() return a guard that you dereference to access the protected value. The mutex is released when the guard is dropped. The guard borrows from the mutex, so any attempt to keep it around longer than you have access to the mutex is flagged by the compiler.

My honest opinion is that it is 2021 and it's about time we let computers manage all of our resources, instead of only memory.

Addressing the last part of your comment, that doesn't sound like my experiences writing Rust at all. Regarding memory management and ownership, the majority of the code I write in Rust, I just type the obvious thing and it just works. The majority of the remainder, the compiler points out that I've done something dumb, and it's a trivial fix. The rest of the time, I'm trying to do something genuinely complicated, and the compiler helps me figure out and verify the work in pieces, rather than trying to cram the whole thing in my head at once, and then convince myself that it's correct. When I'm finished, I feel confident that the compiler will catch errors introduced by future maintenance and refactoring, rather than having to cram it all back into my brain at once every time I touch the code.

To me, in both Rust and Go, the trivial cases are trivial. For more-interesting cases, Rust lets me just do the thing and focus on thinking about my problem at a higher level while trusting that the compiler will handle the details. For more-interesting cases with Go, I have to keep track of a bunch of little details and make sure I explicitly clean up every little resource I ever interact with.


Having said all of that, I do agree that there are many programs where Rust's defaults don't add a lot of value, and it would be nice to have access to shared garbage-collected data without the syntax overhead of Arc<Mutex<T>> or crossbeam_epoch.

I really like the discussion on resource types vs data types in this essay on higher-level rust-inspired languages: https://without.boats/blog/revisiting-a-smaller-rust/


This has been solved since Common Lisp using resource managment macros and similar approaches used by modern lacks.

What Go defer lacks, is the ability like e.g. .NET with IDispose, where I can make of Roslyn Analyzers to break builds when I forget to call using on such resources.

Rust is a good option for kernel code and drivers, beyond that there is no point in throwing away the productivity of having a GC around.


You could have your cake and eat it too if languages with multiple GC heaps had become popular, as it is right now 99% of programming languages have one global heap and that means global pauses from which you cannot escape from. A lot of the time when someone wants to write real time code they just want that tiny little nugget to run without the GC, usually it can be confined to a single thread or maybe a separate pool of threads each with their own heaps while the rest of the application happily uses a global heap.

The fact that you haven't mentioned these low hanging fruit makes me feel like you haven't thought your argument through. You're advocating for a half baked solution even though there is a perfect solution that almost nobody tapped into that would make the GC superior almost everywhere. That's why you are wrong and why nobody should listen to you and people should listen to the creators of Erlang and Ponylang instead.


> My honest opinion is that it is 2021 and it's about time we let computers manage memory already.

In the field where I am working (realtime audio), that is just not possible. I don't want to have a garbage collector pause in the audio thread. In fact, I can't do any dynamic memory allocations at all (without a special realtime memory allocator). Other fields, like medical devices, flight control, embedded systems, etc. have even tighter constraints.

Manual memory management is not only about performance, but also about predictability. This kind of low level control is one of the main reasons why people use C/C++ in the first place.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: