So this is an old article. I wonder if his perception of Rust has changed since it's 1.0 debut.
> Rust is “C syntax” and Nim is Python-ish. Nim wins this one hands down for me.
This is always going to be a really personal choice for most people. I actually prefer all the clarity I get from the curly brackets.
> Nim has Exceptions with tracking (!) and Rust has explicit error handling via the return value. Personally… that feels like a big step backwards, much like error handling in Go. Again, for me, a clear win for Nim. I know others feel differently, but that is ok :)
Rust has made a plain distinction between expected error return behavior (like parse errors) that you can recover from, and panic! which is the closest thing to exceptions. If you come from languages and code bases that suffer from huge amounts of confusion b/c of things like RuntimeExceptions vs. CheckedExceptions vs. Errors (Java) it's a blessing to have strongly typed return values that require doing something with the error case.
Though, errors in rust could be easier to work with, they are a bit painful now. It looks like there is some stuff on the road map to do just this, based on a merge I saw recently.
> Rust is pushed by Mozilla, Nim is a grass root “true open source” language. Having a “backer” might be good, but I like grass root efforts since they tend to be more open and easier to participate in as a “full” member.
Mozilla is a saint for backing this effort. Love them or hate them, it's like Redhat's support of Linux. It's great that there are paychecks paying for this development.
The Rust community is really strong, and doesn't (IMO) suffer from the issues related to BDFL and has open transparent discussions around requested changes.
The main complaint that "Rust is much more complicated to program in." Is still very valid. The learning curve for Rust's memory handling is steep. But this is to be expected when comparing any GC to a non-GC language. GC languages are always going to be easier for the programmer, that's the whole point of the GC. Rust's claim of being memory-safe without a GC is great but it's not an advantage if you can afford the GC.
I think that's a bit oversimplified. The same mechanisms that allow memory-safe manual memory management also eliminate data races without having to use a race detector. They also provide a very flexible form of const correctness, if that's your thing.
I think the cognitive overhead of Rust's memory model outweighs its benefits only if all of the following are true for your application:
1. Your application can afford the cost of GC and a runtime.
2. Your application does not use concurrency, or you are willing to tolerate data race errors, or you can eliminate all data races with a runtime race detector (due to extensive test coverage or similar).
3. The help that compiler-enforced immutability affords to your ability to reason about the code isn't important enough to you to justify the cost.
I think that there are a lot of apps that meet (1), (2), and (3), don't get me wrong. But I also think there are a good deal of apps that meet (1) but not (2) or (3), and they can benefit from Rust as well.
I don't use Rust, so pardon my ignorance, but my impression is that Rust will be very annoying if you're doing something that does not require safety.
For example, these days I write a lot of data processing stuff that is closer to scripting than anything else, and is usually "one-offs" that get scrapped afterwards. But the code needs to be super fast, talk to databases, parallelize across multiple cores and nodes, which rules out many languages. On the other hand, safety is the absolute least concern. I've been using Ruby when it's not performance-sensitive, and Go when it is, and while Go is significantly less pleasant, it gives me decent performance, concurrency and access to libraries. Nim would probably also be ideal, if not for the lack of libraries. But Rust just seems like a terrible choice for this stuff because of the syntactical overhead of its abstractions, in particular memory.
That limits its usefulness. A single language for all my work would reduce context switching and allow code reuse.
In general, my impression is that Rust is good at the complex, heavy stuff, but it doesn't scale down.
Well, since you mentioned performance, the stuff I mentioned also buys you performance. Rust is in a category of language performance that GC'd languages with runtimes or JITs never reach in practice. I don't believe Go will be an exception to this (especially not now, with its lack of an optimizing compiler).
In general, I think if you're looking for "one language to rule them all", that can scale up to the highest performance and down to one-off apps you would write in Rails, you will be disappointed. Even if you don't care about safety. That's because, no matter what people say, static and dynamic typing is a tradeoff. Rust is more statically typed than Go is. Go is more statically typed than Ruby is. Which you should use depends on your circumstances.
A lot of language designers and enthusiasts, from all communities, believe there's a "sweet spot" of language design where your language can scale up or down at will. I don't believe it. That experiment was tried with Lisp and Java and failed. Multiple languages are here to stay.
At the end of the day, all I can say is: If you're happy with Go, use Go. Rust was never designed with all use cases in mind, and I don't see that fact as a failure at all. I see it as just acknowledging reality: trying to make a language that scales up and down indefinitely to all use cases is chasing the impossible, and language enthusiasts who believe their language is the one language that can do that are all mistaken.
But Nim shows that it's possible to have a fast, statically typed language that is also terse and lean and "script-like".
I actually do believe there exists a sweet spot. I just think that during design, too little thought it given to the scalability of languages. And I don't think it's that difficult.
For example, type inference is something of a game-changer, capable of making a statically typed language feel nearly like a dynamic one. Yet it's only recently being applied in mainstream languages, despite the theory not being particularly new.
I also think too little thought it given to the cognitive overhead of syntax. Wirth famously designed his grammars to fit on a single screen. Go (which, like its earlier incarnations Alef, Limbo and Newsqueak, is very much influenced by Wirth) gets this syntactical simplicity right. I thought early Rust syntax showed a lot of promise, but I'm unhappy about the current jungle::of::punctuation.
Nim sacrifices a lot. It's garbage collected with a non-thread-safe GC (last I checked). It has undefined behavior, which makes me not confident that people won't discover security problems. It wouldn't be suitable for the project I'm working on, which has to be as fast as it would be if it were written in C++, free of GC overhead (which does not just consist of pauses), and absolutely memory safe. (That doesn't mean I wouldn't use it for other projects.)
Fixing these issues in Nim would make it much more like Rust. In particular, it would have all the cognitive overhead you're criticizing Rust for.
If you want to show that it's possible to achieve all of the use cases of Rust without the cognitive overhead, just citing Nim won't cut it. You need to (a) explain how to do memory safety without garbage collection (reference counting being, of course, a form of GC) without introducing new concepts like Rust does, (b) explain why everyone who thinks they can't afford GC is wrong, or (c) explain why memory safety isn't important even for security-critical software. I don't think (a) is possible, and I don't think (b) or (c) are correct.
But this is not at all what you said in the parent post. You said "Rust is in a category of performance that GC languages never reach in practice".
This is not a claim about safety.
Every time I have benchmarked something, Nim has turned out faster than Rust - or anything else, for that matter. Yet, Nim does have a GC. But since one can allocate on the stack, and it is per thread, it is not a bottleneck.
About safety: you can tune it with compile time flags, or even annotate per function what checks to leave.
- Non-thread-safe GC is unsafe. The way to make it safe is to make it thread-safe. Thread-safe GC does not have negligible performance overhead. (Actually, non-thread-safe GC doesn't either, not by a long shot, but it won't show up in small benchmarks as easily.)
- Just being able to allocate on the stack is not enough. You need to be able to freely make pointers to those stack objects. The ways to do that are to either (a) accept the lack of safety and admit dangling pointers; (b) use an imprecise escape analysis; (c) lifetimes and borrowing. Every choice has costs.
- Safety with runtime checks is not equivalent to what Rust does. Rust provides the safety without runtime checks. That yields better performance than safety with checks.
I'm not intending to spread FUD about Nim specifically. The more interesting question is whether it's possible to have a safe language with the same performance as C/C++ without any cognitive overhead. I strongly believe, after working in this space for over five years, it is not.
That's not to say Nim is a bad language. There are lots of things I like about Nim. It's just that it won't escape the basic tradeoff between cognitive overhead and safety/performance.
Nim threads don't share memory. Each has its own GC heap with data exchanged through messages. The lack of thread safety is irrelevant when they can't normally get to each others memory. This of course has performance implications for certain types of parallelism. Nim optimizes for a model where workers don't have to have high speed, high volume inter-communication. If you need shared memory it is up to you to add it along with whatever safety measures you need.
Does Nim now deep copy all messages between threads and start every thread with a fresh environment? If so, it's safe. But it is really problematic for any parallelism. (asm.js tried this and developers felt it was unacceptable.) I think you really can't get away from thread-safe GC in practice.
I wasn't comparing Nim to Rust's goals, I was comparing it to my "sweet spot".
From everything I have read (I've followed the development quite closely since it was first announced), Rust's safety comes at a significant cost to the developer, in ways that prevent it from scaling down.
You guys are doing a terrific job, don't get me wrong. But from my perspective this looks like a costly design mistake. Looseness is the key to being able to scale down, and strictness is its antithesis. Rust just doesn't offer a forgiving mode that approaches the kind of ergonomy you get from a less strict, garbage-collected language.
I think Nim made a better, more flexible choice, in offering a kind of graduated safety — you can restrict functions (for immutability, memory safety, exception handling, etc.), but the default is wide open. In my experience, this correlates to the top-down structure of programs: You want the foundation (runtime, stdlib etc.) to be as hardened as possible, because you can afford to spend lots of time on it, since it's the most used code that needs to be the most stable over a longer period of time. Towards the top layers everything should be able to decide whether it wants to be sloppy (fast to develop, but unsafe) or strict (slow but guaranteed to be correct).
> Rust just doesn't offer a forgiving mode that approaches the kind of ergonomy you get from a less strict, garbage-collected language.
Because it's impossible (without sacrificing Rust's basic goals). And Nim doesn't prove otherwise, because Nim doesn't share those goals.
You are framing safe vs. performant as a fundamental tradeoff. It is not, and that is the entire point of Rust. Nim does not allow you to remove that tradeoff. Rust does. That's why Rust is suitable for different projects than Nim is.
In other words, Nim's safety features are not equivalent to what Rust has. They have a performance cost in Nim and don't in Rust. So in order to make your "sweet spot" of a language that scales up and down a reality, then you're going to have to either convince me that I should accept the performance overhead of Nim's features or that I should give up safety.
I don't believe that it is possible to combine a loose, GC'd mode with a memory-safe, non-GC'd mode in the way you want. The best you can do is what Rust already does, with Rc and RefCell. What people observe with Rc and RefCell is that you do indeed get back your freedom to make aliases and mutate at will, but you still have to know how the borrow checker works. So Rc and RefCell get little use in practice, because once you know how the lifetime system works you might as well just use the ownership system instead of paying the cost of Rc.
Even if it acquires ownership and borrowing, Nim will not be able to escape this dilemma, barring some fundamental research advance.
From what I've seen, I'm pretty sure that over the past 8 years of development all usecases, features, and syntaxes were indeed kept in mind for at least some amount of time. Not all of them made it into the final design, though. ;)
Unsafe code isn't for things that "don't require safety". It's for things that are intrinsically unsafe, like calling foreign functions, and the unsafe keyword is basically saying "I promise to be very, very careful".
Unsafe code in Rust is for anything the programmer wants to use it for. If someone is writing their own scripts (like the GP), and wants a strongly-typed system-level programming language, and has explicitly stated they care very little about safety, I see no reason why they couldn't use unsafe Rust code.
> I would however urge Rust people to add a section in their
> documentation called “Error handling” or similar so that one
> can find it without having to read the entire manual!
Exactly, between a powerful type system and macros like try!, you're basically rolling on the same level as Haskell and other advanced languages in Rust.
I'd say you should hold off on that claim until Rust errors are composable (RFC #243). I know it's close.
The macros aren't quite up to the job of safely coping with the tangle of different errors that different libraries can return without writing Go-like error handlers yet.
I disagree. The RFC you are citing is effectively just syntax sugar baking the `try!` macro into the language; it doesn't allow you to express anything you couldn't express today with `try!` and closures.
- Nim had a lot of undefined behavior. Dereferencing a null pointer, for example, led to undefined behavior because of compiling to C.
- Nim seemed to have less flexible data race prevention (though the abstract interpretation stuff that Nim does to determine disjointness of arrays could be more ergonomic than Rust's split_at_mut, albeit more complex). Data races were also more of a problem in Nim because the lack of a thread-safe GC meant that races could result in use-after-free.
- Nim was in the typical "memory safety requires a GC; opt out of GC and you lose memory safety" category of languages (reference counting being a form of GC). Rust, by contrast, retains safety even when GC is not used (which it rarely is).
This was many months ago, so it may well have changed.
> Rust is “C syntax” and Nim is Python-ish. Nim wins this one hands down for me.
This is always going to be a really personal choice for most people. I actually prefer all the clarity I get from the curly brackets.
> Nim has Exceptions with tracking (!) and Rust has explicit error handling via the return value. Personally… that feels like a big step backwards, much like error handling in Go. Again, for me, a clear win for Nim. I know others feel differently, but that is ok :)
Rust has made a plain distinction between expected error return behavior (like parse errors) that you can recover from, and panic! which is the closest thing to exceptions. If you come from languages and code bases that suffer from huge amounts of confusion b/c of things like RuntimeExceptions vs. CheckedExceptions vs. Errors (Java) it's a blessing to have strongly typed return values that require doing something with the error case.
Though, errors in rust could be easier to work with, they are a bit painful now. It looks like there is some stuff on the road map to do just this, based on a merge I saw recently.
> Rust is pushed by Mozilla, Nim is a grass root “true open source” language. Having a “backer” might be good, but I like grass root efforts since they tend to be more open and easier to participate in as a “full” member.
Mozilla is a saint for backing this effort. Love them or hate them, it's like Redhat's support of Linux. It's great that there are paychecks paying for this development.
The Rust community is really strong, and doesn't (IMO) suffer from the issues related to BDFL and has open transparent discussions around requested changes.