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

I personally believe that Rust is an exceptional language and it’s ahead of its time. Seriously, can you think of any other language with as incredible type system, incredible tooling, incredible performance, incredible package management and general package quality, incredible compilation target support, and practically no few backwards-compatibility quirks?

But Rust is a really bad general-purpose language. Because Rust isn’t a general-purpose language. It’s designed for high-performance, low-memory, safe computing. The only reason it’s even remotely considered “general-purpose” is because of how great it is. Unless you actually need to write something that’s high-performance, low-memory, or safe (or there’s a really important package in Rust that you can’t find anywhere else), stick to something like Swift or Kotlin or Java or TypeScript. Or even JavaScript or Python or Lua if you’re only writing small scripts.

-

Rust abstracts almost nothing about the OS. In order to understand Rust, you need to understand at a low-level how computers actually work and how languages compile. You need to understand the what, why, and how of threads, the stack / heap, I/O, endianness, etc.; and language features dynamic dispatch, references, async runtime, memory management etc.. These language features are all implemented in Rust, but they’re explicit, you have to think about them and choose your implementation.

People say that Rust is hard because of the borrow checker but there’s much more than that: dyn Traits (you have to understand when a trait can and cannot be dyn), Unsized types (and why there are so many type parameters, because everything must be sized), closure traits, multi-threaded communication, what async actually does, etc. Heck, we almost got you to manually choose allocation schemes (they’re default type parameters, which is weird because Rust doesn’t have many implicit defaults).

You often face decisions like

- should this trait be a static / type parameter, or dynamic / Box<dyn Trait>?

- Is this value borrowed or owned or Cow?

- Should this be a unique reference / Box or a shared reference / Rc or a shared-across-threads Arc or not a reference at all?

- How do we control this value and communicate across threads, do we make the whole thing thread-safe or message passing or provide a shared Mutex/AtomicBool?

- How do we manage async, do we set up an async runtime ourselves (and if so how do we configure this runtime), or how do we add integration with existing async runtimes?

- Should this be a Cell / RefCell because we don’t want to or can’t pass a single mutable reference around? And when / how do we dereference the RefCell as to ensure it doesn’t get accessed anywhere else until the dereference is dropped?

- Can we do this safely, aka encode it in a way the compiler understands? And if not, how do we a) do it correctly unsafe and b) make the unsafe code as short as possible?

-

And this explicitness is part of what makes Rust so amazing, because other languages make these decisions for you. But every decision has costs, and either they a) incur higher overhead, or b) lose safety guarantees.

e.g. in JavaScript everything is on one thread, so no data-races but no true parallelism; everything is garbage collected, so no use-after-free or double-free but you get GC spikes; everything is a dynamic boxed reference and types are checked at runtime, so no passing complex types and type parameters everywhere, but calls are much slower and there is no true type-safety.

Or in C++, there are multiple threads with shared memory but you must explicitly avoid data-races; there is no GC overhead but you must explicitly manage allocs/frees; you can choose whether to pass/store things as references or values and there’s a type system, but values can be assigned / casted to the wrong types leading to memory corruption.

Rust is the only mainstream language where you can do unsafe-but-efficient stuff, but it’s actually safe because it’s checked at compile-time.

-

But this explicitness also makes Rust a bad general-purpose language. e.g.

If you want, you can pretty much make everything in Rust like this. #[tokio::main] for the implicit async runtime, everything Gc<RefCell<T>> for no borrowing concerns, dyn Any with upcasting / down casting, etc. This is mainly at the cost of a ton of extra syntax. And of course you get higher overhead and lose compile-time guarantees, which are now checked at runtime.

Or better yet, what you should actually do, is write your code which doesn’t need high performance, low memory, or security guarantees in another language. And possibly interface it to Rust code with FFI.

Because Rust isn’t a general purpose language. It tries to be general-purpose, and is to an extent, but it’s ultimately built for niche performance and reliability guarantees, and you just can’t have those without user overhead.



I spent about 10 years as a professional C programmer, userland and kernel on Linux, BSD, and WinAPI systems, and I feel like I have a pretty solid grip on "how computers actually work and how languages compile" (I've written compilers with codegen), and "the what, why, and how of threads, the stack / heap, I/O, endianness, etc.", as well as (from C++) "dynamic dispatch, references, memory management etc.." and, like everyone coding in the 2000s, "async runtimes" as well.

Rust is hard. It's not hard because you have to understand all those things. It's hard because you have to understand how Rust understands all those things. Different kettle of fish.

I'm perpetually leery of "you'd have an easier time with Rust if you had a better grip on the CS of systems programming". No, that's not it at all.

(It's fine that Rust is hard. I recognize the achievements, and see where it's a near-perfect fit; I'm looking forward to Rust kernel code. But then, there's a reason almost nothing I write is in-kernel, even when I need "performance".)


tptacek knows whereof he speaks.

Rust has opinions. They are all at least arguably reasonable opinions. But, often enough, you have sound reasons to do things differently. Then, Rust will fight you.

Rust is a pretty good language, and is getting better, but it just takes a very long time for a language to mature. There are no short cuts.

When Rust is mature it will be very far from simple. People will talk about which language subset they are working in. It goes with the territory. Tools that adapt to the real world get as complicated as the world they serve.


>It’s designed for high-performance, low-memory, safe computing.

Meh. If it had been designed for low-memory computing, it wouldn't have had an implicit static global allocator, or a standard library that panics on allocation failures. Currently you have to choose between having no upper bound on your program's memory usage, or using an allocator with an upper bound and accepting that you'll crash on OOM, or giving up on almost the entire third-party crates ecosystem and write with no_std.

The upcoming effort to stabilize std::alloc::Allocator and the A in `Vec<T, A>` etc doesn't help, because every piece of third-party code that currently uses `Vec<T>` is implicitly using `Vec<T, GlobalAlloc>`, and using other allocators in your own code will do nothing to change that. Heck, libstd's own std::io::Read::read_to_end requires a `Vec<u8, GlobalAlloc>`

Maybe someone in the future will invent some bastard child of Zig's comptime, Zig's explicit allocators and everything else from Rust, and that will be the language to rule them all.


The evolution of JavaScript from "DHTML" to jQuery to "JS: The Good Parts", browser runtime speed improvements and the introduction and development of TypeScript really highlights to me that languages that are complex (e.g. how JS objects work) can be explained, improved on, etc.

I guess what I mean by this is just as C++ got move semantics and auto keywords, it's very possible for a language to choose to evolve in ways that can make code easier to maintain and more expressive. TypeScript or Kotlin are examples of this. Also .NET core and Java since Gradle.

Some things rise in popularity despite their complexity. Objective-C and Swift come to mind... as well as writing truly portable Unix code or Kubernetes.

Personally, I'm waiting for the inevitable "Rust: The Good Parts" that I can feed into my linter and help myself and others on my team understand which parts are minefields of complexity and which parts are incredibly useful once understood.

In JS, there were multiple JS APIs and common practices with global state and more that just fell off the face of the earth. Most IDEs now say what is modern and what isn't, backed with new syntax and conventions that avoid traps in understanding like rebinding this for magic syntax, or using lots of stringly-typed functions.

The only problem I can see is that improvements don't come for free, or quickly. In C++'s case, some of the improvements took multiple decades before they were implemented and half a decade to become widespread and recommended...


I don’t buy it. To write meaningful C that you actually understand requires all the same knowledge of how computers work, yet C is a tremendously simpler language than rust and has an easy to comprehend memory model.

The difference is all the stuff Rust adds on top of fundamentals to make it easier to write correct code with respect to memory usage (in other words, it’s a sophisticated system to make sure you always pair one malloc with one free and don’t make any of the other mistakes C permits partly as a result of its simplicity)


Although I agree with almost everything you said, I still feel rust is a general purpose language.

Most programming projects are single thread, not async, and if you aren't juicing for performance, is pretty easy to write. Even in rust.

I think what makes writing rust for general purpose projects hard, is knowing you can do something better that doesn't have to be better.


Yeah I get that Rust tries very hard to be general-purpose. It also has a great ecosystem, has rare features like proper ADTs/macros and no annoying backwards-compatible quirks, and its one of the best languages for WebAssembly. So there are reasons you might want to write something in Rust that doesn't need to be low-overhead.

But even when you're doing everything single-threaded with Rc and RefCell and Box<dyn>, you can still run into pitfalls. More significant, when you do this your code becomes incredibly verbose. You have to constantly specify over and over again you're doing this the high-overhead way and not the weird optimal way, it gets annoying.

Honestly I think Rust should have an "easy mode", where you everything is implicitly wrapped in ARC / dyn / mutex, and when you write code it's almost like you're working in a truly general-purpose "regular" language. Except it interfaces perfectly with regular Rust and compiles to Rust, so you can still use your favorite cargo packages and even jump into real Rust when you need to.

The issue with that is Rust is already stretching itself kind of thin. So this "easy mode" has to be implemented so it's very simple and you can completely ignore it if you want. Maybe it will just be adding better FFI support to Rust, or maybe it will be another ambitious language like Rust but has better support for implicit easiness.


Rust is hard, from a systems programming perspective, not because of anything about the problem domain but because Rust has an unusually narrow perspective of what systems programming entails. And it is unforgiving for software that has requirements outside that perspective, which happens more often than I think some Rust proponents allow.

Modern systems software architectures are thread-per-core, involve a lot of DMA, etc for performance reasons. That is language agnostic, though most is written in C++ because it is amenable to it. This obviates a lot of the safety features Rust obsesses over. It often feels like the Rust community hasn't acknowledged that this is how systems software actually works these days, or the deficiencies of the language in supporting these software architectures.




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

Search: