Hacker News new | ask | show | jobs
by amelius 1119 days ago
There's a lot to be said in favor of Rust's approach to memory management but closures in Rust suck compared to garbage collected languages.
3 comments

For a long time, people thought closures were only practical in garbage collected languages. I've used closures in C. (Against my will, basically. It was a C library that was best-of-breed and there was no other choice.) Getting the memory management right on them was effectively impossible. You know where the values are created for sure, but creating clean specifications of when the closures were destroyed quickly becomes insane in any real program. Very simple in a 20-line sample, very complicated when you got a long-lived callback interacting with multiple other resources that may or may not have their own "interesting" lifespans.

That Rust can do closures at all, do them in a sufficiently useful way that they are practical, and still maintain its safety guarantees without garbage collection is, in my opinion, in the top five accomplishments of the language. Prior to it actually happening I think it's fair to say most programming language developers would have said it's not possible, that you'll either need GC or the requisite type system will be impractically complex. At least their quirkiness generally fits the rest of the language and isn't much "new" quirkiness.

True, but isn't the complexity of Rust closures essential rather than accidental?

Fundamentally closures are easy in e.g. Go because you don't have to think about lifetimes, at all. As soon as you capture a variable, the GC guarantees it won't be dropped from underneath your feet. With non-GC'd languages that responsibility moves from the GC to the programmer. The trickyness of using closures in Rust seems largely to be the trickyness of managing the lifetimes of captured variables.

Not quite, there are non-GC ways to guarantee memory safety without borrow checking, see languages like Vale, HVM (more a runtime), and Inko.

With those in mind, Rust's complexity does indeed look accidental here. It has its other benefits, but it does make closures a bit more difficult.

Inko uses automatic reference counting, which you can argue about definitions, but I would consider to be GC. At any rate, it's not relevant to whether Rust's complexity is accidental or not, because Rust specifically doesn't do automatic reference counting, and instead uses the borrow checker at compile time.
That's because Rust wants you to use the stack almost exclusively for memory allocation because that's the obvious way to do automatic memory management w/o a GC. So the moment you want to return closures you have to allocate them on the heap (Box them).

Rust is a modern, functional programming language where the programmer works in a straightjacket, and this is most painfully evident the moment you want to use closures like you're programming in Haskell.

In Haskell you don't have to think about stack vs. heap allocation. But w/o a way to make it easier for the compiler to choose stack allocation where that would be safe, Haskell ends up being heap-heavy. Rust takes the opposite tack and is stack-heavy, but unlike Haskell Rust forces you to be explicit about the alternative.

It's not like Rust doesn't have a GC. Arc is a GC after all. It's just that Rust makes it hard to write natural code using the heap and GC.

Think it's pretty dubious to call Rust a functional language at all: most Rust is written in an almost imperative style, but with some extra immutability sprinkled in when easy.

Coming from a functional background, when I first started programming Rust I really tried to leverage FP concepts as much as possible. However, I slowly began to realize that the FP version of the code I wanted to write was more complicated, more lines of code, and slower than the naive imperative version.

Clean FP without GC seems really difficult, and I think Rust team did as good a job as they could given the current state of research.

That sounds right to me, yeah.