As I've said before, Go has the advantage of mediocrity. It's boring as a language, but it does automatically most of the things you need for web back-end stuff. It's garbage-collected and does subscript checking, so you're covered on memory safety. There are stable libraries for most things you need in a web server, and those are mostly the same libraries Google is using internally, so they're well tested. The green thread/goroutine approach means you don't have the problems which come from "async" and threads in the same program; there's only one concurrent task construct.
There's a lot to be said for that. You can put junior programmers on something and they'll probably get it more or less right.
Rust is very clever. The borrow checker was a huge step forward. It changed programming. Now, everybody gets ownership semantics. Any new language that isn't garbage collected will probably have ownership semantics. Before Rust, only theorists discussed ownership semantics much. Ownership was implicit in programs, and not talked about much.
Tying locking to ownership seems to have worked in Rust. A big problem with locking has been that languages didn't address which lock covers what data. Java approached that with "synchronized", but that seems to have been a flop. (Why?) Ada had the "rendezvous", a similar idea. Rust seems to have made forward progress in that area.
I used to say that the big problems in C are "How big is it?", "Who owns it for deletion purposes?", and "Who locks it?" At last, with Rust we see strong solutions to those problems in wide use.
Much work has gone into Rust, and it will have much influence on the design of later languages. We're finding out what happens with that model, what's useful, what's missing, and what's cruft.
In the next round of languages, we'll probably have to deal directly with non-shared memory.
Totally shared memory in multiprocessors is an illusion maintained by elaborate cache interlocking and huge inter-cache bandwidth. That has scaling limits. Future languages will probably have to track which CPUs can access which data. "Thread local" and "immutable" are a start.
> You can put junior programmers on something and they'll probably get it more or less right.
I see junior programmers screw up Go catastrophically. Thinking green threads are magical, they forget the existence of mutexes and the resulting code has data races left and right. Languages like Haskell combine green threads with carefully controlled mutation (be it IORef or STM) to avoid this. Go doesn't, so you still need low-level knowledge like how to avoid deadlocks.
As I've said before, Go gives you the veneer of an easy language when it's in fact full of traps for those who aren't experts.
Most companies, in my experience, want tools that are easy to get right in 80% of cases, and in 20% of cases require experts. Go has the benefit of most benign work is deadass simple to get right. The things you referenced in your post are obviously the remaining 20%.
Rust, as an example, is a pretty difficult language to become productive in. Sure, once you've invested the time, you're equipped to handle a wider array of problems, but most organizations don't particularly care about the top. They care about becoming relatively productive relatively quickly.
Go's approach to certain problems require expertise, it is not a silver bullet. But there are no silver bullets, and the examples you gave require expertise in any language. The issue is that you've kind of just cherry picked those three concepts as proof of complexity, ignoring the boatloads of other examples in which Go's simplicity does add value.
> I see junior programmers screw up Go catastrophically. Thinking green threads are magical, they forget the existence of mutexes and the resulting code has data races left and right....
When I worked at a company that used Go, I saw senior programmers with decades of C++ experience screw Go up in exactly this way.
I see this in Elixir all the time too. Lots of people seem to think that immutability combined with message passing makes their program immune to races. As a consequence I see all sorts of designs with races all over the place.
Yes. Go's locking is really no better than C/C++. There are queues, and there are mutexes. Everybody has those now. Go just has a new story for them. The whole "share by communicating" thing tends to lead to people just putting "tokens" on queues, not the actual data.
But Go does have a run-time race condition checker.
Yes, in retrospect Go's push to "make concurrency easy" ended up being one of its worst attributes; Go's real value is in its simplicity, efficiency, and boringness, all of which concurrency runs counter to. On the other hand, it's questionable whether Go would be as popular as it is today if it hadn't hyped up the concurrency angle.
Mutable parallelism is indeed hard, but you don’t have to be an expert to know the basic rules (lock mutable things) and design your application to minimize concurrent mutation. By contrast, learning Haskell well enough to be productive takes weeks if not months. As for Haskell, it sure addresses parallelism, but it introduces far more significant problems with respect to application development. There are reasons it isn’t widely used in production.
> Tying locking to ownership seems to have worked in Rust
I'm not sure whether I would call it "locking to ownership". But the thread safety guarantees that Rust provides through the type system (`Send/Sync`) are probably the main reason why I would consider using it also for applications which would otherwise favor something along C#, Java or Kotlin to be used. Those languages are for me super productive, and by using them you don't have to worry about memory safety either. However they provide no safety net against threading issues.
And unfortunately threading issues are far too commmon - basically everytime I see an entry to mid-level engineer using threads there will exist at least one issue which will cause some pain later on (because it's invisible, will break in weird ways, and will be hard to find). With Rust the compiler will tell people that something is lacking synchronization.
There will obviously still be issues - e.g. if someone tries to be clever with atomics and misses the correct dependencies and ordering. Or just because some architecture is not perfect and things deadlock.
But there are a lot less of the "oh, I didn't knew this required synchronization at all" issues.
> In the next round of languages, we'll probably have to deal directly with non-shared memory. Totally shared memory in multiprocessors is an illusion maintained by elaborate cache interlocking and huge inter-cache bandwidth. That has scaling limits. Future languages will probably have to track which CPUs can access which data. "Thread local" and "immutable" are a start.
Interesting prediction. It is precisely the model of threading which Nim follows, global variables are thread local and sharing them is restricted. I'm not sure if the Nim designer thought of it in the terms you've described, but I will certainly point this out to him :)
It's the route JavaScript is taking too. JavaScript has traditionally been single threaded, so all the existing types are single-thread only. Multi-threading is being introduced (still very experimental atm) and the only way to have shared memory is through dedicated "shared" types.
I've settled into sort of a hierarchy. If I really want to hack out a small prototype quickly, I use NodeJS. Not having types helps me change things around quickly. If I already have a pretty good idea of my data model but still want to develop quickly, I'll use Go. If it's something 1.0.0+ and I want to make it as reliable as possible, I'd use Rust. My problem seems to be few of my projects ever get to that stage, so I'm mostly writing Go these days...
You still program with types when hacking out a small project with NodeJS. How does moving type errors from compile time to run time help you move faster? It always made me move slower.
> It's garbage-collected and does subscript checking, so you're covered on memory safety.
Go is only memory safe in sequential code, or concurrent code that never accesses shared data from multiple threads. It does not protect against data races as Rust does.
Go needs synchronized and immutable collections, or at least to start trusting users to roll their own. Concurrent slice appends will overwrite each other unpredictably, and concurrent map writes and reads can panic.
What you say is prone to make people who are not familiar with Go think there will not a safe way to accesses shared data from multiple threads in Go. This is not the truth. In Go, multiple threads could access shared data safely. When the code is implemented correctly (which is easy), these threads will never access the shared data at the same time.
Rust just makes this guarantee in the syntax level (at compiling time), which is different from most other languages, but with the cost of slow code complication, rigid, and not easy to learn.
BTW, what you say shows you don't understand basic Go knowledge at all.
> As I've said before, Go has the advantage of mediocrity. It's boring as a language, but it does automatically most of the things you need for web back-end stuff.
Given that Go is essentially a safer, smarter C, it boggles my mind to see it being used for applications where getting the business logic right is much, much more important than performance or complicated bit-twiddling.
I echo this. I've been exposed to some Go at work recently, and Rust on my free time. I've been learning both over last few months.
Go is like C. More like C than any other mainstream language I use. IMO this is bad. C is very verbose and hard to read. Rust is more like C++. Close to hardware but many high level language features.
I could rant a lot about what I don't like about Go, but it boils down to this. No generics, bad package system, bad error handling, bad code generation, bad GC, limiting syntax encourages copy paste and bad abstractions, defying common conventions to be cute (like capitalization in variables), mediocre standard library.
I love Rust more as I get better. I already hate Go for some of the above reasons, I wouldn't even use it if it wasn't for work.
Google hypes Go like crazy, I'm convinced that's the only reason it's popular. I think there's many better options, even Java, that don't have many of the shortcomings listed above
I have written a _lot_ of business logic in go, and I can say that imo it is a superb language for doing so. Vastly better than C, and I’m coming around to it being better even than my beloved python.
I am looking forward that the generics 2020 edition actually make it, as Go is becoming anyway harder to avoid on my domain.
I think ultimately the biggest Rust contribution will be for GC languages (tracing GC | RC) to also adopt some kind of early reclamation, similarly to Swift's ongoing approach, and we will reach a good enough situation and that will be it.
This is true in many aspects. Go shares many common feelings with other popular languages.
However, there are also something in Go those are not mediocrity. Being an almost static language but also as flexible as (sometimes even more flexible than) many dynamic languages is one of those not-mediocrity things in Go.
Beyond a certain scale there are by definition no good programmers or bad programmers, there are only average programmers. So it seems pretty clear to me that depending on them to be experts is a losing proposition.
But which vendor is going to convince the industry to produce software that doesn't depend on the legacy cache coherence stuff so they can leave it off the die? I'm reminded of Itanium where "sufficiently smart compilers" to take advantage of VLIW seemed plausible but never really arrived.
I feel like it should be trivial to make a 'scripting' variant of rust. Just by automatically wrapping values in Box/Rc when needed a lot of the cognitive overhead of writing Rust could be avoided. Add a repl to that and you have a highly productive and performant language, with the added benefit that you can always drop down to the real Rust backbone when fine-grained control is needed.
Why not D? It's suitable for system programming and much better than Rust for non-system programming due to the default GC (but you can disable that where appropriate).
It run fast due to native compilation and program compilation time is much faster than its competitors including Rust and C++.
D also now testing memory ownership support inspired by Cyclone/Rust.
It can interface easily with C, C++ (via DPP), Python and R [1].
> Expressive, pretty powerful, ML and Haskell inspired type system
It's great that they use this, but it's still difficult to program in a purely functional style in Rust the way you would in, say, Haskell, because of memory management. Closures can create memory dependencies which are too difficult to manage with Rust's static tools.
An interesting development in Haskell is Linear Types. It opens the possibility of non-garbage collected Haskell. Still a lot of work left but it’s based on theory similar to that behind the Rust borrower mechanism (if I understand it correctly).
Can you describe what's wrong with haskell, ocaml (which, incidentally, was a major inspiration for rust), or f#? They seem to tick most of the items on your list, though this one I don't understand:
> In higher level code you have almost zero justification for `unsafe`, except you really need a C library.
If you need a c library, you can build a 'trusted' wrapper around it as easily in rust as with any other language.
I adore Haskell, but my personal problems with it:
* Records! Lenses are fine, but a huge complexity increase. I love the recently accepted record syntax RFC though, this will make things a lot nicer.
* I feel like the plethora of (partially incompatible) extensions make the language very complicated and messy. There is no single Haskell. Each file can be GHC Haskell with OverloadedStrings or GADTs or ....
* Library ecosystem: often I didn't find libraries I needed. Or they were abandoned, or had no documentation whatsoever, or used some fancy dependency I didn't understand. Or all of the above...
* Complexity. I can deal with monads, but some parts of the ecosystem get much more type-theory heavy than that. Rust is close enough to common programming idioms that most of it can be understood fairly quickly
* Build system ( Cabal, Stackage, Nix packages, ... ? ), tooling (IDE support etc)
> F#
I admittedly haven't tried F# since .net core. I just remember it being very Windows-centric and closely tied to parts of the C# ecosystem, which brings similar concerns as in my sibling comments about Java.
> if you need a c library, you can build a 'trusted' wrapper around it as easily in rust as with any other language.
Sure, but if that wrapper does not exist, you have to build it yourself. I can say from experience that writing an idiomatic, safe Rust wrapper for a C library is far from trivial, so you lose the "I don't have to worry about memory unsafety" property.
You might like Scala and/or Kotlin. You listed Go, but Go’s type system is very weak, as is Go’s support for immutability, two problems that Scala and Kotlin don’t share.
I conceptually love Scala, but in practice it has a lot of downsides.
* Tied to the JVM
* You constantly have to use Java libraries. Which means you have to understand the Java ecosystem. Which increases complexity a lot, since that ecosystem is very mature but also often deeply layered, complex and full of historical baggage
* It can be used in so many different ways. Nicer Java on one end. Almost Haskell on the other (with cats etc).
* The flexible syntax and multi-model concept leads to very variable and non-coherent code styles and libraries
Kotlin is also great, but the type system is less powerful.
And the same JVM and Java library ecosystem concerns apply. ( I've jokingly heard "we don't talk about Native" multiple times... )
At one point in time, I thought Scala would be this, but it very much isn't. It feels bulky and lacking orthogonality to me, and it's tooling leaves a lot to be desired. (Note: this might be true of Rust too once it is as old as Scala.)
My understanding is as soon as you target on a non-Apple device, you lose a large chunk of the API (foundation). This includes things like file system interaction, date handling, sorting/filtering, networking and text processing.
I did, and I think it's a great language in many ways.
My biggest problem is that, as far as I know, it still is very much second/third class citizen on non-Apple platforms. With little signs from Apple that this will change. (Happy to be corrected here!)
Other (more minor) complaints are the Objective-C baggage and the inheritance system - I enjoy the lack of inheritance in Rust.
Swift does indeed hit a lot of the same sweet points as Rust whilst having automatic memory management and generally more user friendly defaults (at the cost of performance). Alas, it doesn't have anywhere near the library ecosystem that Rust has in most domains, and it's cross-platform support is also quite poor.
> Expressive, pretty powerful, ML and Haskell inspired type system
This is not a reason to use rust. It's like saying "I like Ferraries, because they drive fast", failing to specify WHY you need to drive fast (you usually really don't, unless you are on a race track)
> Memory safe. In higher level code you have almost zero justification for `unsafe`, except you really need a C library.
Java, C#, etc.
> Immutable by default. Can feel almost functional, depending on code style.
Okay, however this isn't a big win in practice. Java has this too with @Immutable and mostly that's enough.
> Very coherent language design. The language has few warts, in part thanks to the young age.
Yes, Rust looks fine. Let's see how it evolves. Coherent language design is however again not a reason to use rust, because it fails to mention WHY you need it and WHY it solves a business problem better than Java, C# or C++.
> Great package manager and build system.
Yeah, pretty much all modern languages have that, so it's not worth to even mention. Rust builds are slow, so there is that.
> Great tooling in general (compiler errors, formatter, linter, docs generation, ... )
Really? Ever used Java or C# tooling?
> Library availability is great in certain domains, ok most.
Compared to what? C++? I don't even think there it's true. When comparing to Java or C# library availability and quality is a joke in Rust.
> Statically compiled (though I often wish there was an additional interpreter/repl). Mostly statically linked.
Yeah... What do you need that for? Again no mention of why that's even useful.
> Good performance without much effort.
Like in Java, C# and C++ you mean?
> Good concurrency/parallelism primitives, especially since async
Async is a paradigm that received a lot of criticism lately. It turns out to be cancerous. Fibers will likely replace it and Java is getting it soon. Otherwise yeah, concurrency is something any modern language should solve and maybe Rust has a head-start here. The whole purpose of Rust is focused around safe multi-threading. However other languages don't sleep. You don't switch your company to Rust just because it does one thing better for a couple of years. Other languages will catch up soon.
You seem to be very convinced that Rusts main competitors are Java and C#. I don't think this is usually true. Rusts main competitors are C and C++. I agree that in most cases where Java and C# are possible options they should be prefered over Rust. There are however many situations where neither Java nor C# are options.
> Programmer’s time is valuable, and, if you pick Rust, expect to spend some of it on learning the ropes.
Of all the arguments, time-to-productivity may be the most compelling. Rust will keep most new programmers from being productive far longer than any other language. I'd say 3x minimum.
What's a little surprising, though, is how many of the difficulties beginners have stem from just one concept: ownership.
Counterintuitively, ownership is quite simple. But what's mind-bending about it is that it doesn't exist in any other language a beginner is likely to have used. Yet ownership pervades Rust, sometimes in very hard to detect ways. And perversely, it's possible to write a lot of Rust without ever "seeing" ownership thanks to the complier.
Eventually, though, the beginner comes face-to-face with ownership without recognizing the trap s/he's fallen into. Ownership isn't something you can "discover" by messing around in the same way that most language features can be teased apart. The term "fighting with the borrow checker" is actually a symptom not of a struggle with a feature but a struggle with a basic, non-negotiable concept that hasn't been learned.
I can recommend this video for getting over the hump:
Besides the learning difficulty, I'd there are some real ergonomic issues related to ownership, such as the difficulty of cloning into a closure [1], or nested &mut receiver calls being rejected despite being sound [2].
"But what's mind-bending about it is that it doesn't exist in any other language a beginner is likely to have used."
This is true about killer features in any language that is above the 'average' on the power spectrum ( http://www.paulgraham.com/avg.html ). In fact, even beyond beginners, I see resumes for people all the time with 20+ years who have only used Java/Python/C/C++. If you are evaluating languages for power or expressiveness, not just tooling/libraries/domain integrations, you're going to have longer ramp up time on average, regardless of the seniority of the developer, because you're by definition looking for features that aren't average (and therefore not mainstream).
I think it depends where you spend time in each language and what projects you do.
I use both regularly, and with Rust my time to productivity is faster because I'm not dealing with a build system first and with splitting code between headers and implementations.
I find rust is also better for letting me keep less of the program mentally in mind since I can localize my thinking about data a lot more.
Where rust is worse for productivity is the learning curve is high for many (though personally I found it much less so than C++ given how large the language has gotten). The other issue is most libraries I deal with are built in C++ so I have to either bridge them or drop back to C++
I'd be interested in this as well. C++ seems like a complex beast and full of legacy code / learning materials. I feel I'd could likely be writing decent Rust code sooner than I could C++.
Rust in prod has been bittersweet for us. Our main goal was to 1) do our job and 2) leverage some of the great promises of Rust.
Deterministic memory management and bare metal performance are great and have been realized benefits. The great promises were realized.
On the “do your job” front though, the lack of a good STABLE library ecosystem has been a real issue and big source of frustration. It seems that most library developers in the Rust community are hackers writing Rust for fun, and I do not say that in a negative way. But the consequence is that things are usually not super well maintained, but more critically are targeting Rust Nightly (which makes sense as Nightly has all the new cool compiler stuff).
Add the scarce professional talent pool, the unavoidable steep learning curve, low bus factor risk... It’s just hard to justify pushing (professionally) more Rust beyond its niche.
With Mozilla pulling out (to some extent), the big focus on Web-assembly... it just feels off if all you want to do is build boring backends.
The contrast with Golang’s “boring as a feature” is quite interesting in that regard.
Time will tell if Rust will make it to the major leagues, or will be another Haskell.
Most libraries don’t target nightly anymore. It certainly used to be the case that nightly had all the cool features everyone wanted, but almost all features that popular crates depended on have now been stabilized. Even Rocket (the most high-profile holdout I know of) now works on stable as of earlier this year.
As for maintenance, as with all library ecosystems it’s a mix. The most popular crates tend to be the most well-maintained in my experience. This is definitely something to consider when taking on new dependencies.
Many things that used to nightly don't anymore. I think the only thing I'm using on nightly is Rocket, and that's set to change soon. May I ask what it was?
The web framework situation has also been a problem for quite some time. No clear stable equivalent to jetty, flask, go's net/http. Great that Diesel (which looks great) is making it to stable. I really wished it was stable 3 years ago.
On that note it would be great if crates.io would have stable/nightly compatibility as a primary concept/badge/filter. I can't think of any other mechanism to nudge library developers into supporting stable as a first class citizen.
I (hobby gamedev) moved from Rust to C a few weeks ago, here's something that made me switch:
1. Compilation time (no more words needed)
2. Forced to use Cargo for managing dependencies. The downside of a good dependency toolchain is that people will use it and there will be tons of indirect dependencies. For cross-platform development it's very common to see one of your indirect dependencies doesn't support the platform you wanted, and there's no way around it (even you know how to fix it you'll have to submit a pr and wait till the merge and crates.io release (takes years), or download the whole dependency chain and use everything locally which is a total mess)
3. Too strict. As a hobby gamedev the thing I value the most is the joy of programming, I don't want to spend all my time trying to figure out how to solve a borrow checker issue (you'll always face them no matter how good you're at Rust, and I don't want to use Rc<RefCell<T>> everywhere), and I just want to be able to use global variables without ugly unsafe wrappers in my naive single threaded application.
As a language I love every aspect of Rust, just ergonomically I don't want to deal with it now.
> (even you know how to fix it you'll have to submit a pr and wait till the merge and crates.io release (takes years), or download the whole dependency chain and use everything locally which is a total mess)
I hope this isn't too much of a tangent given the article's subject is details and specifics. I adore Rust for it's unique combination of features:
- Can write fast, and systems-level code; something typically dominated by C/++
- Makes standalone executables
- Modern, high-level language features
- Best-in-class tooling and docs (As the article points out)
I'm not familiar with another language that can do these together. It's easy to pidgeonhole Rust as a "safe" language, but I adore it on a holistic level. I've found Rust's consolidated, official tooling makes the experience approachable for new people.
I think this is something the article overlooked. Sure, Rust has the ability to be a system level language, but you don't have to use it that way and the breadth of good, albeit commonly immature, libraries is testament to this. Rust enums and pattern matching are some of my favourite features in any language.
Python is often my go to for writing tools, but I mistrust its typing and how it behaves when a library throws an exception - not forgetting that pylint warns if you try to catch Exception. Conversely, Rust error handling feels a bit more verbose, but I worry less about what it does in failure scenarios. I either handle the error or pass it up the stack and carry on with my day.
> Conversely, Rust error handling feels a bit more verbose, but I worry less about what it does in failure scenarios
If you're just writing a tool, you can have main return a Result with Box<dyn Error> and then just use `?` everywhere. The code doesn't really end up that much more verbose other than needing to add a return type everywhere, which can be mitigated with a type alias if needed.
It's the modern high-level language features that get me. Despite Rust technically being a lower-level language, they are so well implemented that I often find Rust more enjoyable to code something in than other supposedly friendlier languages. The killer is the compile times. If Rust had Go-like compile times it would be almost perfect.
The expressions "high-level" and "low-level" are almost meaningless in this context. Rust, like C++, has a broad range.
In any big system, almost all of the code will be doing high-level-ish things no different than you would do in a Python program. At unpredictable points it will need to dip into low-level-ish activities, where it might end up spending the most runtime. If you were writing Python, you might call out to a C or C++ module, then.
It is this broad range that defines a modern systems language. It has organizational features that make a big system manageable, and features to get full value from hardware. People used to try to use C as a systems language, with well-known failings.
You get accustomed to the slow compile times of Rust over time. Then you do some Go stuff and get extremely surprised by how quick it is, almost as if Go's cheating. But it's just rustc being slow :).
In the section on "Rust is big and you have to learn it" the author was too nice to people like me: I'm just not smart enough to manage the demands Rust puts on me while also solving my own problem.
I feel like this is a really serious problem for Rust: the number of people capable of making the right choices among the 10 ways to structure a class is too small. This is also why there's too much dangerously bad C and C++ in the world.
Yea, I think Rust may be hurting currently for conventions or "best practices". The benefit of most "best practices" isn't usually that the opinions actually have any merit, but rather that they are consistent and meld into the background. Has any large company that uses Rust published a well-regarded and well-adopted detailed style guide in the way that Google publishes style guides for the languages they use?
You probably won't find much of written out style guides on Rust, since with rustfmt and clippy, a strongly community endorsed code formatter and linter have been available since (almost) day one. With that, you have most of the "best practices" in form of a tool already.
I think that is correct, but I also find Rust to be an unfriendly language and this is a very widespread problem in software engineering: our tools are generally over-complicated and people are expected to just deal with it as a sort of hazing ritual. We've also come to associate one's worth as a programmer with how many of these arcane and opaque tools they can master.
It's much easier to invent a complicated tool and come up with excuses about why all the complexity was required than to invent a tool which makes people happy and productive. Of course some people are rather weird and they're happy with baffling tools, but I'm talking here about the majority. Garbage collection is the kind of invention which freed countless programmers from having to painstakingly worry about memory management. It's not perfect, as it doesn't handle all resources, but it is doubtlessly a step in the right direction.
Now if you look at Rust, their solution to memory management is to force the programmer to add countless annotations so that the unintelligent compiler can figure out what's happening. This is a step in the wrong direction and I certainly hope that this is only a local optimum that will be surpassed, otherwise the future of system programming looks quite sad.
FWIW, I’ve written lots of Rust, and I’ve only had to use lifetime annotations (which is what I assume you’re talking about) a few times. The vast majority of the time the compiler allows you to elide lifetimes.
I took a lot of heat for that in a recent thread. There's really no convincing Rust's most vocal fans what a mistake it was to focus on fixing so few of C++'s problems when they had the opportunity to fix the its-grammar-is-dogshit problem (and slow compiles times) in the same stroke but completely failed to do so. (Votes were all over the place. At one point that comment was up 10–15 points IIRC even with some opposition, until the Aucklanders woke up and buried it in downvotes. And that was when I was being a lot more polite about it than I am now.) C++ programmers seem to have a permanently altered sense of what constitutes "tastefulness".
Every time I think of the ugliness of Rust, I'm reminded of the comments from Stallman where he acknowledges the elegance of Java as an evolution of C's syntax, even though he never reached for Java for himself, having always stuck to C and Lisp instead. Then he goes on to trash C++.
• Rust is focused on being explicit rather than pretty/elegant. So there's sigilisitic code like `&/&mut`, `move||`, `Arc<T>` for details that are implied in higher-level languages.
• Rust wants to have easy-to-parse unambiguous syntax. This makes turbofish required when you have a type in expression context. You need `->` for the return type.
• Rust uses generics, and angle brackets are ugly. It also mandates `{}` in blocks, but allows skipping `()`, which gives you a high ratio of ugly brackets to the nice round ones :)
• Rust wants to make up for the cost of explicitness by adding abbreviations. So you have `fn` that's both explicit and terse. Instead of verbose `switch/case/default`, Rust's `match` uses patterns and `_ =>`.
Rust designers pay a lot of attention to the syntax, but it's designed from a very practical point of view (it is clear? unambiguous? easy to write? composes with other syntax?), and "does it look neat?" is very low on the priority list.
That's a good way to describe the feel. I think it only truly gets ugly when you do something like Arc<Mutex<Box<HashMap<String, Vec<String>>>>>. But the beauty of "(almost) everything is an expression" makes up for that.
There's a lot to be said for that. You can put junior programmers on something and they'll probably get it more or less right.
Rust is very clever. The borrow checker was a huge step forward. It changed programming. Now, everybody gets ownership semantics. Any new language that isn't garbage collected will probably have ownership semantics. Before Rust, only theorists discussed ownership semantics much. Ownership was implicit in programs, and not talked about much.
Tying locking to ownership seems to have worked in Rust. A big problem with locking has been that languages didn't address which lock covers what data. Java approached that with "synchronized", but that seems to have been a flop. (Why?) Ada had the "rendezvous", a similar idea. Rust seems to have made forward progress in that area.
I used to say that the big problems in C are "How big is it?", "Who owns it for deletion purposes?", and "Who locks it?" At last, with Rust we see strong solutions to those problems in wide use.
Much work has gone into Rust, and it will have much influence on the design of later languages. We're finding out what happens with that model, what's useful, what's missing, and what's cruft.
In the next round of languages, we'll probably have to deal directly with non-shared memory. Totally shared memory in multiprocessors is an illusion maintained by elaborate cache interlocking and huge inter-cache bandwidth. That has scaling limits. Future languages will probably have to track which CPUs can access which data. "Thread local" and "immutable" are a start.
I see junior programmers screw up Go catastrophically. Thinking green threads are magical, they forget the existence of mutexes and the resulting code has data races left and right. Languages like Haskell combine green threads with carefully controlled mutation (be it IORef or STM) to avoid this. Go doesn't, so you still need low-level knowledge like how to avoid deadlocks.
As I've said before, Go gives you the veneer of an easy language when it's in fact full of traps for those who aren't experts.
Rust, as an example, is a pretty difficult language to become productive in. Sure, once you've invested the time, you're equipped to handle a wider array of problems, but most organizations don't particularly care about the top. They care about becoming relatively productive relatively quickly.
Go's approach to certain problems require expertise, it is not a silver bullet. But there are no silver bullets, and the examples you gave require expertise in any language. The issue is that you've kind of just cherry picked those three concepts as proof of complexity, ignoring the boatloads of other examples in which Go's simplicity does add value.
When I worked at a company that used Go, I saw senior programmers with decades of C++ experience screw Go up in exactly this way.
But Go does have a run-time race condition checker.
Green threads, data races, mutexes.
I'm not sure whether I would call it "locking to ownership". But the thread safety guarantees that Rust provides through the type system (`Send/Sync`) are probably the main reason why I would consider using it also for applications which would otherwise favor something along C#, Java or Kotlin to be used. Those languages are for me super productive, and by using them you don't have to worry about memory safety either. However they provide no safety net against threading issues.
And unfortunately threading issues are far too commmon - basically everytime I see an entry to mid-level engineer using threads there will exist at least one issue which will cause some pain later on (because it's invisible, will break in weird ways, and will be hard to find). With Rust the compiler will tell people that something is lacking synchronization.
There will obviously still be issues - e.g. if someone tries to be clever with atomics and misses the correct dependencies and ordering. Or just because some architecture is not perfect and things deadlock. But there are a lot less of the "oh, I didn't knew this required synchronization at all" issues.
Interesting prediction. It is precisely the model of threading which Nim follows, global variables are thread local and sharing them is restricted. I'm not sure if the Nim designer thought of it in the terms you've described, but I will certainly point this out to him :)
You still program with types when hacking out a small project with NodeJS. How does moving type errors from compile time to run time help you move faster? It always made me move slower.
Honest question.
Go is only memory safe in sequential code, or concurrent code that never accesses shared data from multiple threads. It does not protect against data races as Rust does.
Rust just makes this guarantee in the syntax level (at compiling time), which is different from most other languages, but with the cost of slow code complication, rigid, and not easy to learn.
BTW, what you say shows you don't understand basic Go knowledge at all.
Given that Go is essentially a safer, smarter C, it boggles my mind to see it being used for applications where getting the business logic right is much, much more important than performance or complicated bit-twiddling.
Go is like C. More like C than any other mainstream language I use. IMO this is bad. C is very verbose and hard to read. Rust is more like C++. Close to hardware but many high level language features.
I could rant a lot about what I don't like about Go, but it boils down to this. No generics, bad package system, bad error handling, bad code generation, bad GC, limiting syntax encourages copy paste and bad abstractions, defying common conventions to be cute (like capitalization in variables), mediocre standard library.
I love Rust more as I get better. I already hate Go for some of the above reasons, I wouldn't even use it if it wasn't for work.
Google hypes Go like crazy, I'm convinced that's the only reason it's popular. I think there's many better options, even Java, that don't have many of the shortcomings listed above
Deleted Comment
I think ultimately the biggest Rust contribution will be for GC languages (tracing GC | RC) to also adopt some kind of early reclamation, similarly to Swift's ongoing approach, and we will reach a good enough situation and that will be it.
This is true in many aspects. Go shares many common feelings with other popular languages.
However, there are also something in Go those are not mediocrity. Being an almost static language but also as flexible as (sometimes even more flexible than) many dynamic languages is one of those not-mediocrity things in Go.
The type system of go makes doing some "smart" things impossible.
The choice comes down to whether or not you view depending on the expertise of your employees as a liability or an asset.
Pretty sure my client wasn't delighted either in having to pay expensive extra hours for me to cleanup the project.
Sounds like the https://en.wikipedia.org/wiki/Actor_model with actors/virtual threads pinned to cores.
But which vendor is going to convince the industry to produce software that doesn't depend on the legacy cache coherence stuff so they can leave it off the die? I'm reminded of Itanium where "sufficiently smart compilers" to take advantage of VLIW seemed plausible but never really arrived.
Had AMD not been allowed to produce x86 clones and Intel would have pushed those Itaniums everywhere they could, while ramping down x86 production.
Dead Comment
Personally I often use Rust for "non systems-programming" tasks, even though I really wish a more suitable language existed.
Rust has plenty of downsides, but hits a particular sweet spot for me that is hard to find elsewhere:
* Expressive, pretty powerful, ML and Haskell inspired type system
* Memory safe. In higher level code you have almost zero justification for `unsafe`, unless you really need a C library.
* Immutable by default. Can feel almost functional, depending on code style.
* Error handling (it's not perfect by any means, but much better than exceptions in my book)
* Very coherent language design. The language has few warts, in part thanks to the young age.
* Great package manager and build system.
* Good tooling in general (compiler errors, formatter, linter, docs generation, ... )
* Library availability is great for certain domains, decent for many.
* Statically compiled. Mostly statically linked. Though I often wish there was an additional interpreter/JIT with a REPL.
* Good performance without much effort.
* Good concurrency/parallelism primitives, especially since async
* Increasingly better IDE support, thanks to the author of this blog post! (rust-analyzer)
So I often accept the downsides of Rust, even for higher level code, because I don't know another language that fits.
My closest alternatives would probably be Go, Haskell or F#. But each don't fit the above list one way or another.
It’s more of a prototype, but I think it’s in the direction you’re describing.
It run fast due to native compilation and program compilation time is much faster than its competitors including Rust and C++.
D also now testing memory ownership support inspired by Cyclone/Rust.
It can interface easily with C, C++ (via DPP), Python and R [1].
[1]https://dlang.org/blog/2020/01/27/d-for-data-science-calling...
It's great that they use this, but it's still difficult to program in a purely functional style in Rust the way you would in, say, Haskell, because of memory management. Closures can create memory dependencies which are too difficult to manage with Rust's static tools.
https://www.tweag.io/blog/2020-06-19-linear-types-merged/
> In higher level code you have almost zero justification for `unsafe`, except you really need a C library.
If you need a c library, you can build a 'trusted' wrapper around it as easily in rust as with any other language.
I adore Haskell, but my personal problems with it:
* Records! Lenses are fine, but a huge complexity increase. I love the recently accepted record syntax RFC though, this will make things a lot nicer.
* I feel like the plethora of (partially incompatible) extensions make the language very complicated and messy. There is no single Haskell. Each file can be GHC Haskell with OverloadedStrings or GADTs or ....
* Library ecosystem: often I didn't find libraries I needed. Or they were abandoned, or had no documentation whatsoever, or used some fancy dependency I didn't understand. Or all of the above...
* Complexity. I can deal with monads, but some parts of the ecosystem get much more type-theory heavy than that. Rust is close enough to common programming idioms that most of it can be understood fairly quickly
* Build system ( Cabal, Stackage, Nix packages, ... ? ), tooling (IDE support etc)
> F#
I admittedly haven't tried F# since .net core. I just remember it being very Windows-centric and closely tied to parts of the C# ecosystem, which brings similar concerns as in my sibling comments about Java.
> if you need a c library, you can build a 'trusted' wrapper around it as easily in rust as with any other language.
Sure, but if that wrapper does not exist, you have to build it yourself. I can say from experience that writing an idiomatic, safe Rust wrapper for a C library is far from trivial, so you lose the "I don't have to worry about memory unsafety" property.
* Tied to the JVM
* You constantly have to use Java libraries. Which means you have to understand the Java ecosystem. Which increases complexity a lot, since that ecosystem is very mature but also often deeply layered, complex and full of historical baggage
* It can be used in so many different ways. Nicer Java on one end. Almost Haskell on the other (with cats etc).
* The flexible syntax and multi-model concept leads to very variable and non-coherent code styles and libraries
Kotlin is also great, but the type system is less powerful. And the same JVM and Java library ecosystem concerns apply. ( I've jokingly heard "we don't talk about Native" multiple times... )
[0]: https://bootstrappable.org
Kotlin, though, yep, big fan.
My understanding is there is some effort to provide a version that works outside of Apple OSes, but it’s incomplete https://github.com/apple/swift-corelibs-foundation/blob/mast....
If you are looking for static linking stdlib it looks like there’s still an open bug on Linux. https://bugs.swift.org/plugins/servlet/mobile#issue/SR-648
My biggest problem is that, as far as I know, it still is very much second/third class citizen on non-Apple platforms. With little signs from Apple that this will change. (Happy to be corrected here!)
Other (more minor) complaints are the Objective-C baggage and the inheritance system - I enjoy the lack of inheritance in Rust.
Deleted Comment
This is not a reason to use rust. It's like saying "I like Ferraries, because they drive fast", failing to specify WHY you need to drive fast (you usually really don't, unless you are on a race track)
> Memory safe. In higher level code you have almost zero justification for `unsafe`, except you really need a C library.
Java, C#, etc.
> Immutable by default. Can feel almost functional, depending on code style.
Okay, however this isn't a big win in practice. Java has this too with @Immutable and mostly that's enough.
> Very coherent language design. The language has few warts, in part thanks to the young age.
Yes, Rust looks fine. Let's see how it evolves. Coherent language design is however again not a reason to use rust, because it fails to mention WHY you need it and WHY it solves a business problem better than Java, C# or C++.
> Great package manager and build system.
Yeah, pretty much all modern languages have that, so it's not worth to even mention. Rust builds are slow, so there is that.
> Great tooling in general (compiler errors, formatter, linter, docs generation, ... )
Really? Ever used Java or C# tooling?
> Library availability is great in certain domains, ok most.
Compared to what? C++? I don't even think there it's true. When comparing to Java or C# library availability and quality is a joke in Rust.
> Statically compiled (though I often wish there was an additional interpreter/repl). Mostly statically linked.
Yeah... What do you need that for? Again no mention of why that's even useful.
> Good performance without much effort.
Like in Java, C# and C++ you mean?
> Good concurrency/parallelism primitives, especially since async
Async is a paradigm that received a lot of criticism lately. It turns out to be cancerous. Fibers will likely replace it and Java is getting it soon. Otherwise yeah, concurrency is something any modern language should solve and maybe Rust has a head-start here. The whole purpose of Rust is focused around safe multi-threading. However other languages don't sleep. You don't switch your company to Rust just because it does one thing better for a couple of years. Other languages will catch up soon.
Of all the arguments, time-to-productivity may be the most compelling. Rust will keep most new programmers from being productive far longer than any other language. I'd say 3x minimum.
What's a little surprising, though, is how many of the difficulties beginners have stem from just one concept: ownership.
Counterintuitively, ownership is quite simple. But what's mind-bending about it is that it doesn't exist in any other language a beginner is likely to have used. Yet ownership pervades Rust, sometimes in very hard to detect ways. And perversely, it's possible to write a lot of Rust without ever "seeing" ownership thanks to the complier.
Eventually, though, the beginner comes face-to-face with ownership without recognizing the trap s/he's fallen into. Ownership isn't something you can "discover" by messing around in the same way that most language features can be teased apart. The term "fighting with the borrow checker" is actually a symptom not of a struggle with a feature but a struggle with a basic, non-negotiable concept that hasn't been learned.
I can recommend this video for getting over the hump:
https://www.youtube.com/watch?list=PLLqEtX6ql2EyPAZ1M2_C0GgV...
In my experience, Rust productivity shoots up by a lot given a basic understanding of ownership.
[1] https://github.com/rust-lang/rfcs/issues/2407
[2] https://github.com/rust-lang/rust/issues/6268
This is true about killer features in any language that is above the 'average' on the power spectrum ( http://www.paulgraham.com/avg.html ). In fact, even beyond beginners, I see resumes for people all the time with 20+ years who have only used Java/Python/C/C++. If you are evaluating languages for power or expressiveness, not just tooling/libraries/domain integrations, you're going to have longer ramp up time on average, regardless of the seniority of the developer, because you're by definition looking for features that aren't average (and therefore not mainstream).
I use both regularly, and with Rust my time to productivity is faster because I'm not dealing with a build system first and with splitting code between headers and implementations.
I find rust is also better for letting me keep less of the program mentally in mind since I can localize my thinking about data a lot more.
Where rust is worse for productivity is the learning curve is high for many (though personally I found it much less so than C++ given how large the language has gotten). The other issue is most libraries I deal with are built in C++ so I have to either bridge them or drop back to C++
Deterministic memory management and bare metal performance are great and have been realized benefits. The great promises were realized.
On the “do your job” front though, the lack of a good STABLE library ecosystem has been a real issue and big source of frustration. It seems that most library developers in the Rust community are hackers writing Rust for fun, and I do not say that in a negative way. But the consequence is that things are usually not super well maintained, but more critically are targeting Rust Nightly (which makes sense as Nightly has all the new cool compiler stuff).
Add the scarce professional talent pool, the unavoidable steep learning curve, low bus factor risk... It’s just hard to justify pushing (professionally) more Rust beyond its niche.
With Mozilla pulling out (to some extent), the big focus on Web-assembly... it just feels off if all you want to do is build boring backends.
The contrast with Golang’s “boring as a feature” is quite interesting in that regard.
Time will tell if Rust will make it to the major leagues, or will be another Haskell.
As for maintenance, as with all library ecosystems it’s a mix. The most popular crates tend to be the most well-maintained in my experience. This is definitely something to consider when taking on new dependencies.
There's no doubt that Rust as a language checks all the marks (the complexity of Rust is IMO unavoidable providing such a feature set).
What most discussions about the greatness of Rust miss:
1. Not language features, but teams and processes make successful projects.
2. Stability, matureness and a great standard lib like Go or ecosystem like Java beat language features.
Rust is a great playground for enthusiasts. But for your production apps you're probably better advised to use the boring stack.
The web framework situation has also been a problem for quite some time. No clear stable equivalent to jetty, flask, go's net/http. Great that Diesel (which looks great) is making it to stable. I really wished it was stable 3 years ago.
On that note it would be great if crates.io would have stable/nightly compatibility as a primary concept/badge/filter. I can't think of any other mechanism to nudge library developers into supporting stable as a first class citizen.
1. Compilation time (no more words needed)
2. Forced to use Cargo for managing dependencies. The downside of a good dependency toolchain is that people will use it and there will be tons of indirect dependencies. For cross-platform development it's very common to see one of your indirect dependencies doesn't support the platform you wanted, and there's no way around it (even you know how to fix it you'll have to submit a pr and wait till the merge and crates.io release (takes years), or download the whole dependency chain and use everything locally which is a total mess)
3. Too strict. As a hobby gamedev the thing I value the most is the joy of programming, I don't want to spend all my time trying to figure out how to solve a borrow checker issue (you'll always face them no matter how good you're at Rust, and I don't want to use Rc<RefCell<T>> everywhere), and I just want to be able to use global variables without ugly unsafe wrappers in my naive single threaded application.
As a language I love every aspect of Rust, just ergonomically I don't want to deal with it now.
For what it's worth, cargo supports patching dependencies without maintaining the whole dependency chain locally. See here: https://doc.rust-lang.org/cargo/reference/overriding-depende...
- Can write fast, and systems-level code; something typically dominated by C/++
- Makes standalone executables
- Modern, high-level language features
- Best-in-class tooling and docs (As the article points out)
I'm not familiar with another language that can do these together. It's easy to pidgeonhole Rust as a "safe" language, but I adore it on a holistic level. I've found Rust's consolidated, official tooling makes the experience approachable for new people.
I think this is something the article overlooked. Sure, Rust has the ability to be a system level language, but you don't have to use it that way and the breadth of good, albeit commonly immature, libraries is testament to this. Rust enums and pattern matching are some of my favourite features in any language.
Python is often my go to for writing tools, but I mistrust its typing and how it behaves when a library throws an exception - not forgetting that pylint warns if you try to catch Exception. Conversely, Rust error handling feels a bit more verbose, but I worry less about what it does in failure scenarios. I either handle the error or pass it up the stack and carry on with my day.
If you're just writing a tool, you can have main return a Result with Box<dyn Error> and then just use `?` everywhere. The code doesn't really end up that much more verbose other than needing to add a return type everywhere, which can be mitigated with a type alias if needed.
Deleted Comment
In any big system, almost all of the code will be doing high-level-ish things no different than you would do in a Python program. At unpredictable points it will need to dip into low-level-ish activities, where it might end up spending the most runtime. If you were writing Python, you might call out to a C or C++ module, then.
It is this broad range that defines a modern systems language. It has organizational features that make a big system manageable, and features to get full value from hardware. People used to try to use C as a systems language, with well-known failings.
I feel like this is a really serious problem for Rust: the number of people capable of making the right choices among the 10 ways to structure a class is too small. This is also why there's too much dangerously bad C and C++ in the world.
It's much easier to invent a complicated tool and come up with excuses about why all the complexity was required than to invent a tool which makes people happy and productive. Of course some people are rather weird and they're happy with baffling tools, but I'm talking here about the majority. Garbage collection is the kind of invention which freed countless programmers from having to painstakingly worry about memory management. It's not perfect, as it doesn't handle all resources, but it is doubtlessly a step in the right direction.
Now if you look at Rust, their solution to memory management is to force the programmer to add countless annotations so that the unintelligent compiler can figure out what's happening. This is a step in the wrong direction and I certainly hope that this is only a local optimum that will be surpassed, otherwise the future of system programming looks quite sad.
Every time I think of the ugliness of Rust, I'm reminded of the comments from Stallman where he acknowledges the elegance of Java as an evolution of C's syntax, even though he never reached for Java for himself, having always stuck to C and Lisp instead. Then he goes on to trash C++.
• Rust wants to have easy-to-parse unambiguous syntax. This makes turbofish required when you have a type in expression context. You need `->` for the return type.
• Rust uses generics, and angle brackets are ugly. It also mandates `{}` in blocks, but allows skipping `()`, which gives you a high ratio of ugly brackets to the nice round ones :)
• Rust wants to make up for the cost of explicitness by adding abbreviations. So you have `fn` that's both explicit and terse. Instead of verbose `switch/case/default`, Rust's `match` uses patterns and `_ =>`.
Rust designers pay a lot of attention to the syntax, but it's designed from a very practical point of view (it is clear? unambiguous? easy to write? composes with other syntax?), and "does it look neat?" is very low on the priority list.