Why Not Rust?
matklad.github.io
matklad.github.io
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.
Perhaps it's experience with other languages, or the fact that I grew up when that sentence started with C++, but I honestly found rust pretty easy to get up to speed in.
Am I an expert? No, and until I get the chance to use it for a living that won't change. But getting from "I want to do X and Y with this language" to having done so was straightforward enough.
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 have said it many times: Thinking Go is easy to master is considered harmful. Holding such opinion (thinking Go is easy to master) will make you understand Go shallowly and do many mistakes in Go. One the other hand, if you learn Go seriously, Go could help avoid these mistakes easily (and with a happy programming experience).
https://elixir-lang.org/getting-started/mix-otp/ets.html#rac...
But Go does have a run-time race condition checker.
Yes, but every language has a different number of traps, and each trap has a different difficulty, and each person has a different likelihood of encountering each trap.
So when someone says that, they mean that it's worse than the average (well known) language on one or more of those axes.
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.
I do the same but I don't feel slower doing it. I mean I have to move around between function bodies and callers, for example, when I'm hacking in python, so the two don't feel that different to me (moving between a user and a provider or definition of something when hacking on code).
But what does feel different to me is if I hack up some go and get a compile error, that feels MUCH faster and nicer to me than hacking up some python, running for a minute, and half way through hitting a runtime type error.
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
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.
Rediscovering protocol oriented programming like Objective-C in 1986, Interlisp-D in 1983, CLU in 1975 and Standard ML in 1997?
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.
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.
Unfortunately Rune and Dyon[0] are dynamically typed, which isn't so attractive to me.
More promising are Gluon and Mun, both of which are statically typed. Of these two, Gluon has a somewhat alien syntax if you're coming from Rust (it notes it's inspired by Lua, OCaml and Haskell) so Mun is probably a better choice...but it seems very early, and the website notes as much (to serve the needs of a Rust-scripting-language I'd want seamless interop between it and Rust, which isn't quite there).
So I don't think there's anything in this space right now, but there are some promising options.
If you're wiling to go a little further afield, I'm kind interested in assemblyscript[3] - it's 'just' another WASM-based language so it's not a huge leap of imagination to believe there could be tooling to enable the seamless Rust interop. Just a matter of effort!
[0] https://github.com/PistonDevelopers/dyon [1] https://github.com/gluon-lang/gluon [2] https://github.com/mun-lang/mun [3] https://www.assemblyscript.org/
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.
from memory:
trait Prop<'a, T> {
fn p<F: Fn(&'a T) -> bool>(f: F) -> Self;
fn eval(&self, &'a T) -> bool;
}
enum LiftedBool<'a, T, F: Fn(&'a T) -> bool> {
Base(Box<F>),
And(LiftedBool<'a, T, F>, LiftedBool<'a, T, F>),
Or(LiftedBool<'a, T, F>, LiftedBool<'a, T, F>),
Not(LiftedBool<'a, T, F>),
}
impl<'a, T, F: Fn(&'a T) -> bool> Prop<'a, T> for LiftedBool<'a, T, F> {
fn p<F: Fn(&'a T) -> bool>(f: F) -> Self {
// this fails because `F` in the impl doesn't necessarily unify with `F` in the trait definition
// it can be made to work with `dyn` for the function argument but requires boilerplate to actually use
}
fn eval(&self, &'a T) -> bool {
// obvious evaluation by cases
}
}
this example can be fixed to work via `dyn` for the argument on the trait function and the struct itself but it becomes increasingly painful to actually work with. this kind of generic work is not at all ergonomic but very easy in Haskell.> 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.
FWIW I did a small project recently in f#, using .net core (on linux), and it never seemed like a second-class citizen. Can't speak to the c# ecosystem, though.
> > 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.
Right. My question is, why is this a mark in favour of rust? It doesn't seem to distinguish it at all from the other languages you mention.
Aren't macros, especially proc macros these days in Rust having the same effect? Personally I feel like this is a tradeoff every language has to play with: you either limit to a special way of writing, or adding some sort of ad-hoc system that enables rewriting syntax and even to a degree, semantics.
* 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... )
Totally agree with the plethora of ways to use it being an issue, though. “Classic Scala” and “let’s write Haskel is Scala” is a real split in the community, and a significant downside. Then you have Akka too, though at least that solves different problems (highly stateful AND highly concurrent, which is a pretty rare mix).
That's not so true in my experience. You have to reach for Java libraries in the same cases where you'd have to reach for C libraries in other languages. When you're using a Java library it's usually to do something specific (because using the big Java frameworks makes no sense), so the complex parts of the Java ecosystem don't really affect you.
This problem is bigger in Clojure which has a smaller ecosystem than Scala and is why I don't pick it for writing "real world" applications in it.
The first point is true, but IMO not a big deal for most applications. Scala native exists, but is very immature. Still, if you’re writing web services, data processing jobs, etc., I don’t see much of an issue with depending on the JVM. Definitely an issue with things like command line tools, or apps with super tight resource requirements, but that’s not the case for a tonne of software.
Why be concerned about this? Either way you're making a hard dependency on some languages runtime, be it go, rust, or the jvm and I see it sticking around a lot longer than the others.
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
FWIW, looking through that list you linked to, it seems most things are complete apart from relative edge cases (NSGeometry), fairly Apple-specific (Bundle), or replaced by something newer (NSCoder). The big one that jumps out to me as being a larger issue is the URL authentication stuff… so I guess steer clear if you need to handle basic auth/Kerberos/etc. The XML parsing being mostly incomplete is a bummer, too. :(
Ultimately, though, I’ve found Swift on Linux to have everything I need for what I’ve been building with it. Obviously, YMMV, but (and not just for you, anyone else reading as well), I wouldn’t be scared off just because Foundation isn’t 100% complete.
###
Edit: that being said, this is also just a defense/rebuttal to the comment I’m replying to. Swift isn’t the answer to all projects; I still reach for Clojure as my first choice for large API projects, mostly due to my own preferences.
I’ll have to give it another shot.
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.
I'm a bit leery to label Swift as a "systems language," as I feel that systems languages should be a wee bit closer to the bone (like ObjC); but that also means that I am not a fan of using systems languages to write application code. I wrote both levels in C and C++ for years, and HATED using those languages for app-level code. Swift, to me, is an almost ideal application language.
But I don't write system code anymore, and have no desire to. I love writing app-level code, and love Swift.
It's been a long time since I've had the pleasure of employing a language I actually like using.
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.
Not too many anymore, and the space for these situations shrinks over time, rather quickly.
20 years ago, almost everything was implemented in C or C++, for all platforms. The computers didn’t have extra RAM for the GC overhead.
Web sites were the first to migrate, probably because security. Desktop GUI apps followed.
On mobile, before iPhone was launched in 2007, people used a lot of C or C++ there. PalmOS only supported C, Symbian SDK was C++ based, most Windows Mobile apps were written in C or C++. For the same reason, not enough resources for a GC. We now have gigabytes of memory in these devices and it’s no longer the case, on Android it’s almost 100% Java or Kotlin, on iOS it’s Swift.
This is happening with videogames, see Unity3D. Even low level soft-realtime multimedia is good enough for .NET now, here’s an example where my C# implementation delivers same performance as C implementation of VLC player: https://github.com/Const-me/Vrmac/tree/master/VrmacVideo
Pretty sure in a couple of years once MS improves a few relevant things in their .NET core (they need better constant propagation, and NEON support) we’ll see high-performance numeric stuff following the trajectory. The hardware SIMD support in .NET core 3.1 is a good step in the right direction.
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.
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.
The web frameworks seem to be coming along nicely. I think the main reason for stagnation was the unstability and flux around async. Actix (stable), Warp (stable) and Rocket (stable in the next major release as all nightly features have stabilized) seem to be the strongest contenders at this point.
I initially misread this comment; what you are saying (and what is true) is that all the features it was using are now stable, but the version that uses them hasn't been released yet. I initially thought you were saying "once all its features are stable."
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.
With Python, you can create a rich hierarchy of errors using inheritance, but to achieve something similar in Rust the best option I've come up with is maintaining an enum that wraps the different error types. It feels clunky, so I hope there's better ways.
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.
There are many, many routes to failure. Staying off them doesn't guarantee success; it's table stakes.
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.
But there's more to style guides than formatting and Clippy lints. Some designs will pass Clippy (or worse, lead to Clippy warnings far past the point where a redesign is economical) but be inefficient, inflexible, or have a poor effect on compile speeds.
One major problem I experienced in my last job (where I worked on production rust) was the insane amount of macros and proc macros used. This was an originally C++ heavy shop so they leaned on it more than, say, a python shop moving to rust would.
This led to terrible compilation times and confusing code only certain engineers could maintain (I'm guilty here - I've since left and I know of one proc macro I wrote that will cause headaches to anyone who uses it).
We need an opinionated style guide. The language is so complex we probably need multiple, with different trade offs.
Is there any language that allows full-fledged macros that doesn't have projects that descend into this?
This seems like a standard problem with programmers deciding 1) they don't like the language semantics and use macros to adjust that (then why are you using the language) or 2) over-factoring before you really have enough reuse.
Most of the popular style guides from big companies I know from other languages are much more one a trivial level where each of the bullet points can be quickly explained and generally isn't more complex than a clippy lint (which is why I mentioned that).
Definitely would like to see a more in-depth guide to patterns for structuring large code bases. I've finally dialed things in pretty well in my own head, but it took quite a while to get there, with a lot of lessons learned the hard way.
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.
So languages like Rust are relegated to picking up the new greenfield codebases and some few codebases where engineers felt courageous enough to introduce it.
Overall, the signs are good though. I think it's much easier to onboard new engineers to Rust rather than to the company specific dialect of C++.
I've been experimenting with "bidirectional" (for lack of a better word) refactoring in autocxx where the bulk of the C++ implementation stays the same but wrapped in a safe Rust interface (using autocxx to generate the underlying interop) that is then reexposed by another run of autocxx to the rest of the C++ code. New Rust code can use the safe interface while old code is refactored to use the now (slightly) safer wrapper over the Rust interface. This allows for very intentional, piecemeal refactoring across the code base.
Rust's compatibility with other languages like Python and Javascript is already, imo, second to none thanks to a few well designed libraries and the macro system. The only language that comes close is C# with its IronPython integration, except with Rust it's a lot easier for library authors to integrate runtimes. It took me less than a day to construct a JS/Python monstrosity with an absurd call stack (Node => Rust => Python => Rust => Node => Rust => Python) when I needed to wrangle together a bunch of legacy proprietary libraries in webapp form.
Like you said, the signs are good for Rust's interop story in general.
Calling Rust from C and vice versa is fairly straightforward and there are automatic code generators for doing so. Things just get wrapped into an `unsafe` block and that gives you the same guarantees you'd have with C++ (none, basically). From there you can start to gradually rewrite critical parts in safe Rust and profit from the cargo ecosystem along the way. In that sense there's even an advantage for Rust over C++ in my book.
There is no "C/C++" code. Using the expression mainly generates confusion, most usually in the person saying it.
While Rust is a systems programming language, it has a very advanced Traits system. This makes it, in my opinion, more powerful than your average scripting language (Python, JavaScript, etc...) I have recently used Rust for something that I should have, normally, used JavaScript or Python for. Based on my experience, I probably gained in development time because I didn't do much debugging. Rust code just works, it's simply amazing.
> Complexity
One way to approach this, is to actually enjoy this complexity because there is real theoretical computer science behind it. It's not complexity for its own sake.
> Compile Times
This one is really annoying even with a powerful processor.
> Maturity
I'd argue that since many high profile companies like Facebook, Google and Microsoft used Rust to start new projects (like Libra, Fuchsia); Rust is stable enough to be used in a production capacity. You'll probably benefit more if the project is new and thus have less time dealing with C compatibility.
Some people enjoy complex languages and some don't, and while the complexity in Rust does serve a purpose -- supporting the borrowing system and a design philosophy that is similar to C++'s -- there is "real theoretical computer science" behind any language design. The theory doesn't tell us which approach is good and which is bad, just what properties a language has. Some designs, usually on the more complex end, lead to many properties that are of interest to people who study those properties, and so there is some correlation between people interested in studying formal systems and people who prefer complex languages.
The biggest hole here is the complete absence of any dynamicism. Rust types do not have any real runtime presence.
Concretely, there is no way to dynamically check whether this value implements some Trait. This is used in Golang (Copy -> WriterTo) but is impossible in Rust without manual implementation.
Rust itself is production ready, but not the ecosystem.
But rust might suffer due to scope creep because someone felt the need to teach rust to all ruby rail / javascript webshits.
I'm not entirely sure, but have the feeling that Rust's "cons" are mostly short term draw backs and the "pros" could be really valuable in the long run.
Broadly speaking, Rust is good at trees and bad at graphs. This implies real tradeoffs, real design decisions. There are many software components that Rust is just not good at, by design.
[1] https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.D...
I guess it is either Electron (HTML/CSS/JS) + 200MB Chromium engine or Qt5 (C++) for now.
Most languages never get one, and content themselves with piggybacking off of WxWidgets or JavaScript (or whatever).
I'll give an example of the latter which I find compelling. OpenType shaping (one of the subproblems of text layout) is a notoriously difficult problem, and almost nothing can compete with HarfBuzz, which is written in C++. Yet there is already one pure Rust alternative being used in production, Allsorts (used in Prince XML), and another promising one in development (rustybuzz, being developed by RazrFalcon, who has a track record of shipping ambitious 2D software).
But by all means, if you can implement your applications using native widgets, please do so. You'll be able to make them accessible, internationalized, etc. with much less work.
The really crazy thing is that the group that eventually birthed Rust had an acceptably performant Electron alternative even before Electron existed—and it supported both HTML and native-ish looking widgets. But they completely underinvested in it and eventually killed it—insisting that they knew better and that no one really needed or wanted it. In the meantime, Electron came along and is now so ridiculously pervasive that it's regularly brought up in conversation—even where its of very little relevance—just as a consequence of people looking to complain about how popular it is. The only thing we hear from the former group who strangled their baby, though, is how dire the outlook is for them and their influence on the future of computing.
This may well be true, but using a memory-safe language is never, ever the goal. The goal is creating correct and secure programs -- that have as few bugs and security flaws as possible/required -- as cheaply as possible. While a memory safe language in the style of Rust is one means toward that end, that eliminates an important class of bugs at the cost of language complexity, it is not the best way toward that goal, at least not that we know, and it is certainly not the only one [1]. I.e. the hypothesis that if I want to write a program that's as correct as possible/needed as cheaply as possible then I should necessarily use the language that gives me the most sound guarantees regardless of the cost this entails is just some people's guess. It's hard to say if it's a good guess or a bad one because it's clear that we're talking about specific sweet-spots on a wide spectrum of options that could be very context-dependent, but it's still a guess, with good arguments both in its favour as well as against.
> In Rust, there are choices to be made, some important enough to have dedicated syntax.
Not only that, but those choices are exposed in the type signature and are, therefore, viral. Changing some internal technical implementation detail can require changes in all consumers. This is not a problem with the type system -- on the contrary, Rust's type system ensures that all the changes that need to be made are made -- but it is a fundamental problem with all low level language. They all suffer from low abstraction, i.e. a certain interface can represent a smaller number of implementations than in high-level languages (even if the choice is not explicit in the type, like, say, in C, the usage pattern is part of the interface). But Rust's choice to expose such details in the types has its downsides as well.
> If you use C, you can use formal methods to prove the absence of undefined behaviors
C now also has sound static analysis tools [2] that guarantee no undefined behaviour with a nearly fully automatic proof that scales to virtually any code size and requires relatively little effort, certainly compared to a rewrite.
[1]: Another low-level language with an emphasis on the same goal of correctness, Zig, takes an approach that is radically different from Rust's and is so simple it can be fully learned in a day or two. Which of the two approaches, if any, is better for correctness can only be answered empirically.
[2]: Like https://trust-in-soft.com/, from the makers of Frama-C
This is an interesting topic unto itself, described in one of my favourite pieces of technical writing of all time:
How Swift Achieved Dynamic Linking Where Rust Couldn't - Alexis Beingessner - https://gankra.github.io/blah/swift-abi/
- Fast compile times
- Easy build system. Must be parallel, saturate all cores, and handle distributed builds as a built-in first-class feature. Should be able to produce static binaries or dynamically linked binaries. Should be able to consume binary dependencies (that means a stable ABI).
- Optimized, fuzzed, highly-tested, standards compliant web stack built-in
- Ability to compile CUDA kernels, built-in
- Automatic memory management with an escape hatch for where manual is necessary
- Rich metaprogramming and compile-time reflection
- Created by or funded by a large U.S. corporation so that PMs are likely to choose it.
- IDE/LSP language server/editor plugins maintained by the language designers
Failing this, you are an academic language, not a real world industry programming language in 2020.
Microsoft and Google have both reported that memory safety errors make up about 70% of their security vulnerabilities.
They did not release those numbers.
> Why did Google choose C/C++ for Fuchsia after evaluating also Rust?
A couple of things here:
* We don't actually know if they did evaluate Rust for the kernel or not.
* At the time they would have been making that evaluation, Rust was one year past 1.0, and was significantly less mature than it is now.
* They had a bunch of people who had experience with existing kernels that were in C and C++, and in fact, based Zircon on one of them: LK.
All of these are good reasons to pick what they picked. However, there's one more thing here, and that's that Fuchsia is a microkernel, and so the kernel is a lot smaller than in other OSes. Rust is used for a bunch of components that would be in the kernel if Fuchsia was a monolithic kernel.
> How come, Actix announced a new version with memory leaks fixed?
Well, first of all, memory leaks aren't a memory safety issue, so this would be irrelevant. However, Actix had actual memory safety issues as well, and did fix those. This happened because the author used a lot of unsafe that they didn't need to. This is pretty straightforward though; it was easy to find those issues because of the way that Rust works, and then they were fixed. That's kind of the point!
There are times you need manual memory management. Then there are other times you don't (the vast majority of use cases). Rust forces you to constantly pretend that memory management matters, which gets quickly tiresome.
Not everyone is working on Chromium or Windows. The reality of the matter is that for most real-world software development security doesn't matter at all.
Rust can actually be built with Bazel. The tooling an ecosystem for doing that is nowhere as good as Cargo and I really wish the Rust team would focus more on supporting other build systems as first class (i.e. not having half the conventions for packages being set by Cargo - things like build.rs output syntax, the various environment variables), since they bring a lot of mature tooling and performance to the table.
At some points, I'd basically have to wait 10-15min.. so I'd go for a walk or grab coffee. Then I'd switch to javascript and it felt like going at the speed of light. To each their own.
Not to pile on, because Rust is still in a fragile condition, but the point that Rust cannot, and never will be able to call, typical C++ libraries deserves a boost (no pun intended).
This matters because C++ has more facilities to encapsulate powerful semantics into libraries than any other language. People write libraries in C++ that cannot be written in other languages, routinely.
This makes it usually impractical to integrate Rust code into an existing modern C++ codebase, unless it implements a wholly independent subsystem. That matters because effectively all of the most demanding systems-level work, today, is conducted in C++, not C (old OS kernels and databases excepted).
The longevity problem also deserves attention. The normal, expected fate of any new language is to die. It practically takes a miracle to survive, and we have no way to predict miracles. So, we don't know if there will be anyone to maintain Rust code written today.
What will it take to survive? It comes down to numbers. Rust has an excellent adoption rate for a language at this stage. To survive, it might be enough were the rate to increase by two orders of magnitude.
You don't get that just by more and better publicity. It needs change. But the changes needed are, by experience, very, very unpopular among existing Rust users.
Rust will never displace C++ or C. C++ does things Rust can't. C will dwindle only as its user base retires, because C users actually like it for its failings: it makes them feel tough (or something). Rust is an overwhelmingly better language than Java or Go, and the world would be a better place if Rust were to displace them.
But neither of those has the specific problems that the borrow checker demands be solved. They have other, graver weaknesses. So, for Rust to displace them, its advocates will need to change their approach to appeal to users of those languages.
That will require at least a different build model that admits an order of magnitude faster builds, and a looser use of the borrow checker that generates less frustration. It might need accommodations to integrate in Go and Java projects, maybe including support for a JVM target, virtual call mechanism, and import of foreign Go and Java modules.
To get any of that, the project will need to excite people now in those environments with the prospect of easing their pain. Rust's advantage there is that their pain is great, and Rust could ease it.
100% agreed
>> and the world would be a better place if Rust were to displace them.
Rust needs a standard lib like Go or ecosystem like Java. Production stuff needs to be stable. Especially Go gets this right.
Rust with a standard lib like Go could rule the business apps world. But Rust doesn't even include an async runtime. It's not a problem, if Rust wants to stay in the niche where it is. Also, Rust advocates should not wonder why the adoption in the general purpose web space is far below its potential.
Could you expand a little bit on that?
C++20 just got "concepts", which make this capability vastly easier to use when creating a library, and enable some new capabilities along the same lines, while greatly improving compile-time error reports.
What it needs are active measures to increase its adoption rate by orders of magnitude. Hype on HN only goes a little way.
This has been my experience as well. I've been trying out Rust for a week now and as an experienced programmer, the complexity of the language is overwhelming. That said, you don't need to know all language constructs to get started. In the last week, I've been able to build a simple but useful cross-platform GUI app after reading only the ownership/lifetime section of the Rust book.
But yes, it is not for everyone and it certainly isn't a simple language to pick up.
May I ask using what?
The only thing that explains it is an influx of web programmers who are used to this.
I guess the problem is "unsafe" blocks. Someone else can probably elaborate better, but if you have pure Rust code with no unsafe blocks, you shouldn't have any memory errors. But large systems written in Rust might still benefit from finding memory errors dynamically rather than statically with something like ASAN.
Languages with good compile times include:
- C[1] (unless you go crazy with the preprocessor)
- Go (unless you go crazy with code generation)
- D (unless you go crazy with templates)
1. With TCC or similar. Gcc/clang compile times are just ok (though better than with c++; and interestingly, though gcc is slower for c++, it's faster for c).
It is bad because rustc emits quite a lot of LLVM IR and lack of binary libraries / library caching.
As other computer engineering concepts (e.g. referential integrity in database systems), the lack [of implementation] of a feature (e.g. foreign keys) doesn't make a system that needs that concept simple - it just shifts it to the application level.
Modern Ada/SPARK also support Rust style 'safe-pointers': https://blog.adacore.com/using-pointers-in-spark
For my line of work. I rather use what is available out of the box in SDK installers and respective IDEs, no need to add extra complexity.
Now in general, I think Rust's sweet spot is on kernel, drivers and maybe composition engine, anything else GC languages with low level features, e.g. Swift, D, Nim, Crystal, .NET Native, Java (after Panama/Vahlala), Go (assuming 2020 generics get in) are a much more productive way to tackle problems.
EDIT: Just like with FP, I am a firm believer that eventually all mainstream languages will have some form of affine types, and Rust will have played the role of a stepping stone into the adoption of Cyclone and ATS ideas for low level coding.
Also should be noted that Rust lacks the necessary certifications.
If a bug could kill someone, you should (to the greatest extent possible) have a proof that such bugs are impossible.
The thrust of the article is, from the abstract, that Rust should consider altering the compiler and the crates ecosystem to better enable the safety features that Rust champions, because ‘ results indicate that software engineers use the keyword \unsafe in less than 30% of Rust libraries, but more than 75% cannot be entirely statically checked by the Rust compiler’.
I would assume this, but also
> is there some issue with the underlying empirical analysis of Rust’s library codebase?
this is hard to tell, because
> The paper, upon which the video is based is linked in the description and is easily accessed
I don't think this is true, or else, I am missing it somehow. Where is the paper? I don't see it linked from the page that's linked in the description, and a quick google of the title didn't turn it up for me either.
I recently had an idea: What if i made a subset of Swift and a transpiler that outputs simple C? The C output would make it a great contender for integrations with existing code in the 'systems programming' genre.
Swift is such a lovely language, far simpler than Rust, but being tied to apple's ecosystem and LLVM makes it difficult to use for many of these areas.
This would also be great for embedded, which is something i'm also passionate about.
End result would be: productive + simple + safe language, great for systems/embedded IoT stuff.
Anyway i've submitted the idea for an R&D grant, we'll see if it goes anywhere. Would love to hear people's thoughts.
Interesting. I did not know this. I would have thought Box<dyn Foo> would have achieved PIMPL. Is that not the case?
Oops. Good catch. Fixed. :)
Sounds like this is a fixable problem though? That’s nice. I really appreciate the amount of attention Rust compile times is getting. (Especially because relative to C++ they aren’t actually that bad!)
The outer exposed API is called on `this` which then uses the internal `pimpl` pointer to actually call a function.
While it may speed up building, I have found it incredibly clunky to use in C++ and prefer to write my code without it, choosing instead for longer compilation times.
PIMPL is just a tool. There’s no argument here that it should be used in all or even many cases. The fact that C++ has this tool and Rust does not is interesting.
PIMPL indeed has overhead. Both when writing the code and when calling the code. If a program crosses the PIMPL boundary many times then the calling overhead can be meaningful.
However if the API is small and the program spends more time “inside” the PIMPL than crossing the boundary the runtime overhead is trivial.
A key benefit of PIMPL is that, in some cases, you can have a very lightweight header than clearly expresses an API without making users include a ton of dependencies they don’t actually need. I shall not express my opinion on C++ headers.
Historically I have not liked PIMPL. My current project benefits from it a great deal. Most projects probably would not. Like I said, it’s just one of many tools.
The consumer code then can use std::unique_ptr<iSomething> (the interface needs a virtual destructor for that) without including the implementation.
Just like PIMPL, this allows to replace implementation without breaking the ABI. Requires much less boilerplate compared to PIMPL. Compiler will automatically setup these function pointers without requiring to manually write the wrappers for public methods. As a nice bonus, compiler forces you to fully implement the interface or the std::make_unique line won’t compile.
> Rust also lacks an analog for the pimpl idiom, which means that changing a crate requires recompiling (and not just relinking) all of its reverse dependencies.
I really hope `extern type` could help with this. edit wrote https://github.com/rust-lang/rfcs/pull/2984#issuecomment-695... on how I think it could.
Well, that's how you get remote code execution vulnerabilities in CS:GO [1]. If you're doing anything at all over the network you absolutely need to care about memory safety. This doesn't mean that you need to use Rust of course, but security is still an issue.
Honk
Now of course if you use a garbage collected language, then you aren't in control of memory, so it's less of an issue. But if you are using C++ for high performance applications such as gaming, then you are in control of it and need memory safety.
We care about memory safety as much as any program. Engine code doesn't need fast compile times, scripts do, but they are scripts.
Where I have worked, we have used scripting for fast iteration.
To clarify: We have fast scripts when you want to iterate, so don't need fast engine compile times.
And what most people forget is that complexity works against safety: the less the programmer is distracted by other issues (e.g. memory management), the more they can focus on security.
"The borrow checker is more complex than simple malloc/free calls therefore it is less safe."
Which is clearly wrong.
A GC'd language, however, is usually not preferred for systems work. However, most applications don't fall in that category.
Not always. Java programs have fewer memory-management issues than C programs, and they tend to be less severe.
Memory leaks are as easy under GC as in C.
What are you referring to?
> C programs have a poor record of memory usage errors
Right. The C language allows for all manner of memory-related failures that are impossible in a language like Java. Use-after-free, double-free, and dereferencing NULL, are all undefined behaviour in C. In Java, the first two are impossible, and attempting to dereference null throws an exception. It's possible to do screwy things with Java finalisers, mind.
> the habit of Java programs routinely needing orders of magnitude more memory to do similar work is well known
Sure, but that's a matter of efficiency, with no bearing on memory safety.
> Memory leaks are as easy under GC as in C.
They're still possible, but I sincerely doubt they happen with equal frequency between the two languages. I don't know of a good study on this empirical question though.
Wouldn't no checks equal less recognized bugs and therefore in turn lead to less secure code?
Expressive languages invites people to have their own dialects, their own little version of the language. It also invites people to be too smart for their own good and especially for the team's good, resulting in code that's very hard to debug and maintain for other people than themselves. I've seen in the game industry with C++ how bad it can get[1]. Because in practice there's pressure, deadlines, temporary stand-ins "helping" other teams to finish their sub-projects. There just isn't always time to be strict, so code rot will always sneak in. Of course this happens in all languages, but I find it much less extensive in so-called "boring languages".
[1] Because of this I'm actually really surprised that a few big game industry companies have recently adpoted Rust, because the main problems afaik were code complexity over time and build times.
Furthermore, if you think that this problem is solved by just hassling junior devs a bit you're painfully wrong. Some people with great experience are still prone to this, some times as a matter of style.
My favourite example: see the following code in Go
containsVal = false
for _,i := range array {
if (i == val) {
containsVal = true
}
}
This is one statement in any language with reasonable abstraction capabilities:python: `val in array`
Javascript: `array.includes(val)`
Java: `array.indexOf(val) != -1`
C++: `std::find(array.begin(), array.end(), val) != array.end()` (Actually C++ one is already quite verbose and little distracting, but sure you can implement it better in C++)
Go is only so-called modern language in which standard library cannot provide trivial abstractions to convey intent. Instead you have to use interface{} escape hatch or use codegen.
The 'Go is readable' meme is misguided.
In that particular case, it's just fine to workaround these things with interface{} and move on to more important stuff. Or perhaps consider to use a map if you find yourself constantly needing to pick out an entry from a slice.