Hacker News new | ask | show | jobs
by zenhack 2169 days ago
I think a lot of the best the best things in Rust don't really have anything to do with low level programming per-se. And I find that for most applications, even with all of the extra goodness, the lack of a GC totally craters productivity. Its not because I'm fighting the borrow checker -- I got the hang of it pretty quick. But it means that every API is complicated by the need to think about lifetimes and ownership, having to think about different types of smart pointer, etc. It means you have to manage all of these silly little details which for most applications are pretty irrelevant.

This isn't a criticism of Rust; it's specifically designed for applications where those things do matter. But for the overwhelming majority of apps they don't, and I'd reach for a different tool.

I've found that once I get into a rhythm and I've been working on something in Rust for a bit, it doesn't feel that hard to deal with all that. And a few times I've thought to myself "hey maybe the GC isn't actually buying you all that much?" But then I go back to a GC'd language and watch just how much faster stuff gets done. It's not even close. I think it's one of these things where your brain doesn't notice the time that goes by when you're doing what is essentially mindless busywork.

Part of this is also having come from doing a fair bit of stuff in Haskell, Elm, and a bit of OCaml; the best "high-level" language features are inspired by that language family (including enums) and so it feels like a better control for the difference a GC makes vs. js and friends. It makes a big difference.

3 comments

What's puzzling is why a language designed for memory safety and low-level control and performance is even being considered for web development where they had the former all along and they generally don't care about the latter. Or if they do they use Java, Go or throw a couple dozen more servers at the problem.
It's because it also has expressiveness features that Java and Go don't.

* Better handling of "null"-ness

* Sum types

* Stricter/different error-handling

* Move semantics, which can actually be nice for some APIs outside of any performance considerations

Kotlin checks a couple of these boxes, but then is also GC'd, so also gets rid of a lot of "noise" that would be in the equivalent Rust code.

For typical backend junk, I'd be Kotlin first, but I'd definitely consider Rust if performance (non-IO) was a concern.

With all the "Rust is as productive as X" and "If your data structure/logic won't work in rust, it was bad design" bullshit the silent majority of us are accustomed to see on HN..
With Java you only need to throw more RAM at the server. The performance is perfectly acceptable compared to something like Python or Ruby. You can always squeeze out more performance with Rust but the primary benefit is the lack of a GC.
I don't understand your last sentence. What benefits does lack of a GC have other than more performance?
A bit offtopic for HN, but that's a really nice username. (Just looking at that, I can definitely see how you'd find those things puzzling!)
> But it means that every API is complicated by the need to think about lifetimes and ownership, having to think about different types of smart pointer, etc. It means you have to manage all of these silly little details which for most applications are pretty irrelevant.

Rust gives you the features to auto-manage these "silly little details", you simply have to opt-in to them with a bit of boilerplate. Stuff like Rc<RefCell<T>> and the like is there for a reason.

You still have to deal with the fact that existing APIs use a broader range of types; using Rc everywhere in your own code doesn't save you from dealing with library interfaces.

Also "stuff like" is kind of the problem; all of the smart pointer types have differences that matter, and none of them is general enough to cover all use cases. Arc<Mutex<Box<T>>> comes the closest to a "general" solution, but the boilerplate is rather a lot just for the sake of not having to think about this stuff, and you still can't use it if T isn't Send.

You're really picking a fight with the language if you insist on avoiding making these decisions, and in the end it will just slow you down even more than going with the grain. And to add insult to injury, if you write all your code like that it'll likely be slower than OCaml or Haskell anyway. Rust can't keep up with a good GC on allocation throughput; the performance advantages come from managing things yourself (and avoiding heavy allocation in the first place).

Rc and friends are useful, but they don't make the problem go away.

As I said, it's not that it's even really all that hard. But it is time consuming.

In a sense Rust could have tried to become a "tiered" language with 2 standard libraries: one low level that allows precise control on heap allocations (including custom allocators) and another higher level that relied on a GC. They could both be used at the same time.

There would also be a compiler attribute #![no_gc] that make it so that you have to provide your own implementation of the GC runtime to use GC types. (similar to how executors work for futures)

D tried that one. I don't know it very well, but I believe it turned out not to be a good idea.