If you forced me to pick between Zig and Rust for a long-running project though, I'd pick Rust 10/10 times for the simple fact that it has been stable for more than a decade and already has momentum and funding behind it. Zig is a cool language - one that I've actually written more of than Rust - but it hasn't hit 1.0 yet and still has significant churn in both the language and standard library.
Rust is complex.
That is not a statement anyone can deny. Unless you're a Rust-bro "hey man I learned it so I’m baffled how you can't see it's super simple ..... etc etc" - often the implication that you're not very smart if you think Rust is complex.
Of everything that I have learned about programming, this is the BIGGEST lesson: - complexity is bad, avoid complexity. Complex is not the same as "sophisticated", which implies necessary intricacy. Complex means unneeded unnecessary cognitive load - things made harder than they should be when it could have been avoided - that's complexity. If you are writing complex code then you're writing bad code. And Rust is complex.
It is sad that we did not end up with a SIMPLE programming language that solves the memory safety problem.
What we needed was Rust-- i.e Rust without all the non-safety related extras that make it different - it's all the non-safety add ons that makes Rust into a Rube Goldberg machine.
I am not a savant by any means, and yet I was able to get up and running with Rust relatively quickly. What Rust isn't is ergonomic, in that Rust gets very annoyed with the ways that one might want to structure their code. Trust me, I got bit by the borrow checker countless times, and it did grate on me.
As a result, there are many tasks that I would avoid using Rust for, tasks where both speed and safety aren't critical. But if neither is a priority, the list of alternative languages I can resort to is quite long, much longer than Zig, C++, or C. And in the cases where both are a factor, I would consider being needled by the compiler to be a feature and not a bug.
Compared with what? C++? It is not.
We did, arguably. Those languages are called JavaScript, Python, Java, C#, etc. Those languages tend to be eschewed in certain niches, though, and it's there that simplicity tends to be harder to achieve.
> What we needed was Rust-- i.e Rust without all the non-safety related extras that make it different - it's all the non-safety add ons that makes Rust into a Rube Goldberg machine.
I think it might also be worth considering that some people find those "non-safety related extras" a good thing. Dropping backwards compatibility and/or familiarity, just like everything else, is a tradeoff, and that tradeoff might be worth it if you think the resulting semantics are nicer to work with.
Traits? Nope. We need some way for code reuse. Classes cannot be made memory safe without extra cost (at least, I don't know how can they). And they are not less complex either. Templates like C++? More complex, and doesn't allow defining safety interfaces. No tool for code reuse? That will also severely limit the safety (imagine how safe Rust was if everyone would need to roll their `Vec`).
The borrow checker of course cannot be omitted. ADTs are really required for almost anything Rust does (and also, fantastic on their own). Destructors? Required to prevent use after free.
Async can be removed (and in fact, wasn't there in the beginning) which is a large surface area, but even today it can mostly be avoided if you're not working in some areas.
I don't think anybody can deny Rust is complex, but most often it's inherent complexity (what you call "sophistication") given the constraints Rust operates in, not accidental complexity.
Somehow most of the libraries in the Rust ecosystem seem to interoperate with each other seamlessly, and use the same build system, which I didn't have to learn another unrelated language to use! Astounding!
Says who? You can totally do code reuse using manually-written dynamic dispatch in "rust without traits". That's how C does it, and it works just fine (in fact, it's often faster than Rust's monomorphic approach that results in a huge amount of code bloat that is often very unfriendly to the icache).
Granted, a lot of safety features depend on traits today (send/sync for instance) but traits is a much more powerful and complex feature than you need for all of this. It seems to me like it's absolutely possible to create a simpler language than Rust that retains its borrow checker and thread safety capabilities.
Now whether that'd be a better language is up to individual taste. I personally much prefer Rust's expressiveness. But not all of it is necessary if your goal is only "get the same memory and thread safety guarantees".
Rust can monomorphize functions when you pass in types that adhere to specific traits. This is super-handy, because it avoids a bounce through a pointer.
The C++ equivalent would be a templated function call with concept-enforced constraints, which was only well-supported as of C++20 (!!!) and requires you to move your code into a header or module.
Zig can monomorphize with comptime, but the lack of trait-based constraint mechanism means you either write your own constraints by hand with reflection or rely on duck typing.
C doesn't monomorphize at all, unless you count preprocessor hacks.
> That's like saying Rust has GC because GC libraries/runtimes can be implemented in/for Rust.
Quite a few have already been implemented and are available as libraries exactly as you describe, today. I wouldn't phrase that as "Rust has GC" because that might imply that the GC is required. But if your application might benefit from GC, it's certainly available in the language. I might say "GC is optionally available in Rust if desired" to be more accurate.
> I think it's fair to expect that it will stabilize in time.
The allocator work is facing a lot of obstacles, unfortunately. I prefer it to be unstable for as long as it needs, though.
> Quite a few have already been implemented and are available as libraries exactly as you describe, today. I wouldn't phrase that as "Rust has GC" because that might imply that the GC is required.
This is exactly my point. Rust does not prevent GC by any means, but Rust also does not encourage GC by any means. The same goes for custom allocators, aside from the highly-unstable Allocator API.
Got any links? Sounds like interesting reading. Seriously. Kind of thing I come here for. I'd really appreciate it. Or I can ask the AI for some.
> I prefer it to be unstable for as long as it needs, though.
Sure, same. Fully baking things is a process, and it's nice when things are fully baked. So I agree. I think Rust's async could be a bit more ergonomic, though it wasn't too difficult to wrap my head around, and sort of shockingly simple to implement a basic no-std async executor (~10 lines), so maybe I'm coming around. I was pleased to find out that it was simple enough that I could do it, and understand how it worked, as I try to do with all my microcontroller code, and wasn't dependent on a big async runtime like Tokio if I didn't need it's features (or bloat).
This Github thread is also interesting (transitively reached from the above Reddit thread):
https://github.com/rust-lang/rust/issues/32838#issuecomment-...
What would the minimal set of features be, in your opinion?
> For example Zig which instead of introducing a new metaprogramkming language, it uses...... Zig - imagine using the same language instead of inventing a new additional language with all the accompanying complexity and cognitive load and problems.
Zig probably isn't the best comparison since Zig doesn't try to achieve the same level of compile-time memory safety guarantees that Rust aims for. For instance, Zig doesn't try to statically prevent use-after-frees or data races.
That being said, as with everything it's a question of tradeoffs. Zig's metaprogramming approach is certainly interesting, but from what I understand it doesn't offer the same set of features as Rust's approach. For example:
- Zig's generics are more similar to C++ templates in that only instantiated functions are fully checked by the compiler. Rust's generics, on the other hand, are completely checked at the definition site so if the definition type-checks the author knows it will type-check for all possible instantiations. Rust's approach also lends itself to nicer error messages since everything a generic needs is visible up front.
- Zig's comptime isn't quite 1:1 with Rust's macros. comptime is for... well, compile-time computation (e.g., reflection, compile-time branching, or instantiating types). Macros are for manipulating syntax (e.g., code generation or adding inline support for other languages). Each has things the other can't do, though to be fair there is overlap in problems they can be used to solve.
In any case, metaprogramming approaches are (mostly?) independent of memory safety.
> And result and option and move by dedfault - none of these things were needed but they all add up to more complexity and unfamiliarity and cognitive load.
I don't think Result/Option are that complex (if at all) since they're trivially derivable from discriminated unions/sum types/enums.
I'm also not sure how move by default is necessarily "more complexity... and cognitive load"? Maybe as a result of unfamiliarity, perhaps, but that seems more a property of a person than a language, no?
So what you're saying here is that you don't understand that Rust's rules around memory ownership, aliasing, and mutability are what allow the language to provide deterministic compile time memory safety without runtime cost. If you figure out another way to guarantee memory safety at compile time with zero runtime overhead, you should write a paper and start another language around it!
https://en.wikipedia.org/wiki/Capability_Hardware_Enhanced_R... exists, and is an exciting, laudable effort, I think. But requires hardware support as well as language modifications.