My theory is that every technology that is too complex gets replaced with something that does the same thing more simply. You see this relentlessly in the JavaScript ecosystem where waves of too-complex tools get rapidly replaced with something else, only to be swept away again when someone finds an even more simple way to do the same thing.
This must be the fate of Rust - eventually it will be replaced with a language that gives the memory and thread safety without the melt-your-brain complexity.
I want a simpler version of Rust.
It's too hard.
Is it possible to create a truly safe language without the dizzying complexity? Or is it the complexity a baseline requirement?
I wonder if there is a subset of Rust that can be extracted someone into a new, minimal language with the core benefits.
This exactly how I felt the first two times I tried it, but I never really just dove in. The third time I just dove in. The learning curve is straight up, but once you are over (took me about a month), Rust isn't really all that hard. I can write it as fast as I can write Python typically, so that friction you probably feel will go away...entirely. Sure, there are a few corner cases where the type system gets hard and the more you care about every cycle of performance the more of those you have, but if you are just looking to code Rust as a high level language, it really isn't that hard. Knowing that everything is likely to work when it compiles is really good feeling too.
Tips/Concepts:
- Form a mental model of the rules/memory model of the language, this will help you get to an understanding, and once you understand you don't have to remember the rules.. you could write them yourself.
- The analogy I used to understand borrowing: it is like a read/write lock. Any data structure can be read any number of times (&), but it can only be written once at a time (&mut)
- Borrowing "locks" the position of the data structure and this is one of my favorite features. Imagine you have a reference to the middle of a data structure and then it suddenly moves... this can't happen in Rust as borrows "lock" the struct from moving
- Lifetimes aren't magic and you are in no way defining the lifetimes themselves with lifetime annotations. All you are doing is helping the compiler see which references are backed by what values already. Once you understand which these are lifetimes become easy.
- Eventually you will have an aha moment.. and you will wonder what was so hard. You will have to think about memory in a lower level way, but only to learn it, after that it is just natural
In my experience learning Rust (and in watching others learn Rust) that first "aha!" moment is actually rather misleading.
That moment comes when one learns how to technically accomplish any task in Rust. One knows the minimum set of tools needed to refactor enough to work around the borrow checker's limitations. Learning what the borrow checker prefers becomes second nature and, as you say, becomes natural.
However, the journey isn't over there. One still has to learn the costs they paid to fit their design into what the borrow checker prefers. For some situations, the cost is low (CLI tools, data transformation, numeric analysis, etc). For other situations, the cost can be alarmingly high: one learns that the borrow checker is surprisingly anti-abstraction, causes brittle APIs, and can sometimes introduce artificial complexity compared to the optimal solution in other languages. One sees this a lot in programs with unavoidable inherent state. There are also sometimes problems working with teams, because the borrow checker likes to cause refactors with very wide blast radii that conflict with others.
Of course, this varies wildly person-to-person, according to the prevalent use of unsafe, Rc, etc.
Understanding how to work within the borrow checker isn't enough. The real challenge comes in figuring out when to work within it, and when to work outside it, in my opinion.
> The real challenge comes in figuring out when to work within it, and when to work outside it, in my opinion.
You don't need to feel bad about "working outside" the borrow checker, and maybe that's a signal that needs boosting. I've been using Rust for ages and I'm not averse to cloning things when I need to. It's cool that you can create zero-allocation libraries by threading lifetimes everywhere, and I certainly appreciate all the library authors who go the extra mile by doing so internally to their libraries, but in my application code it's easy to tell that a given thing isn't going to be a bottleneck and isn't going to be negatively impacted by a little copying and allocating.
Yep, very much this. I code async CLI apps and I seriously don't care that the CLI args and the data structures they get transformed to are cloned 250 times at startup because after that 99% of the actual heavy-duty code is working with channels and references with nearly zero memory pressure. Memory usage gets bumped up by about 2% after the first 1-2 minutes of operation and it stays constant for next 11 hours during which I've monitored it.
I don't even need to benchmark anything. The apps are working ultra fast on a Celeron J450 CPU at ~20MB RAM usage and barely hit 10% CPU (since they're mostly network-bound).
No need for premature optimization. I haven't dived in the lifetime hell of Rust yet but I'm already quite productive and I will only be using zero-allocation techniques if my current code proves inadequate the the ever-increasing demand that these apps will soon face.
> This exactly how I felt the first two times I tried it, but I never really just dove in. The third time I just dove in. The learning curve is straight up, but once you are over (took me about a month), Rust isn't really all that hard.
I will say that the compiler got WAY better over the last couple of years.
The quality of the error messages went way up, and the number of times I had to do something stupid because the compiler couldn't infer something went way down.
To me, the compiler getting so much better was what finally got me over the hump.
Yes, Rust is quite different from typical languages thanks to ownership and borrowing. It often takes people several attempts to learn it before their perspective shifts and it finally clicks for them. But this perception of Rust has less to do with Rust being "hard" than it has to do with Rust being "different". To be sure, Rust has hard parts even after you've internalized the differences, but IMO by that point it's not substantially different from mastering the hard parts of other languages (especially other systems languages, none of whom can get away with entirely sweeping the details of the machine under the rug).
I can see how it could be hard for someone used to high level languages and who doesn't care about how memory actually works "under the hood". The secret to learning Rust is really the same as C/C++: you have to learn to think of memory in a low level way -and- learn Rust's safety rules for dealing with that. I think it does take someone very dedicated to the task, more so than is required to learn say Python, for example. However, I find it much more rewarding.
I don't think the ownership model, borrowing, references etc are actually particularly difficult. They're well explained in "The Book", even better so in Jim Blandy's "Programming Rust", and at least in principle can be got over in an afternoon. Admittedly it can all knotty with a lot of closures and async etc, but it's certainly not the source of the difficulties for me.
The question at the end of the day is whether that "hardness" is required or accidental. My gut feel is that Rust does have some accidental complexity--poor support for static/globals, poor support for placement new and alternate allocators, inability to transmute equivalent types well, etc.
However, my gut feel is also that a lot of Rust's "difficulty" is the fact that people really don't know how to program C/C++ even remotely correctly, and Rust is simply slapping them in the face with that fact.
It increases complexity if the state is intrinsically global. Hardware resources, for example, which are commonly directly managed in systems code, are intrinsically global state. You can't allocate another hard drive or physical network interface. I've seen impressively convoluted code that existed solely to pretend that global state isn't actually global state, because that was seen as bad, even though it behaved exactly like global state.
Rust is comfortable with immutable statics. The existence of a global readable Doodad is no problem. For mutable access static Doodad Rust requires unsafe, because you will probably shoot yourself in the foot.
Imagine there's a static NetworkIF nic which is immutable. Maybe we can call predicates like nic.up() to find out if this interface is up, or nic.packet_counts() to get some statistics about packets. But it's immutable, so we can't call nic.change_addr() or nic.disable_packet_filters() as those require mutation. If it was mutable we'd need unsafe Rust to try to call those, because doing so means being very careful about who does what and when they do it or we'll get in a mess.
Because of "interior mutability" we can have a Rust static which is immutable from a Rust point of view, and thus safe, but where we can get at mutable internals. For example static Mutex<NetworkIF> nic. Now it's protected by a mutex. We can lock the mutex, and thus get mutable access to the NetworkIF, until we release that lock. If somebody else has the lock then we'd block waiting for them to release it.
With just mutable globals everywhere and a larger team it's easy to end up with clashes where two subsystems both think they "own" a shared mutable object and treat it accordingly then are surprised that this doesn't consistently do what they expected.
One thing in particular that is gratuitously difficult in Rust is to do some very expensive runtime initialization, place the result into static memory, and then do read-only things to that memory after it is initialized.
Rust really does not like that pattern (it always wants you wrap that and takes locks to it with the associated performance hit) and yet it is an extremely common thing to need to do (embedded communication stacks, graphics card shaders, etc.).
global state is often necessary. Abusing it is always possible, but every startup-sized application footprint I've ever been in we eventually have to add an exception to the no-globals linting rule for some very legitimate reason in some specific place.
The crate ecosystem (oncecell, lazystatic) lets you completely sidestep this limitation for now anyway before better const behavior eventually gets added to stable
The point is, Rust is vastly easier than C++ when you start factoring in the complexity of debugging nasty design choices that comes to bite you in the back and you can't really fix without tearing the whole application apart.
Yesterday I was fighting the borrow checker because it wasn't allowing me to pass a reference in an async function. After a while it dawned me that if that thing I was attempting to write would have been accepted, it would have caused a crash in some rare, but not impossible, circumstances.
It saved me potentially countless hours of debugging and wrapping my head around the fact that my smart abstraction crashed every once in a while, something that happens to me everyday with C++.
Rust is hard, but it's less hard than it's alternatives.
Dunno, maybe it is Stockholm syndrom from using C++ since 1993, but I definitly find easier to understand template metaprogramming than the upcoming GAT, or the whole Pin and PhatomData stuff.
> Pin is a way to create self referential structs, by pinning memory and preventing moves.
This, exactly. Pin works more or less like a C++ class with a deleted move constructor. Copies may be allowed if clone/copy (aka, the implicit/explicit copy constructor) are defined, but move and RVO are not.
Lots of Rust's concepts can often be described in terms of C++'s techniques, but more often than not in C++ it tends to be a hack, while Rust has a formalism for that, like Pin for instance. In C++ you can have moveable self-referencing structs because you can update the self references in the move constructor, but moves in Rust are always memcpy - which is saner IMHO than the whole move constractor and rvalue shenanigans.
How does this work? I want to wrap POSIX semaphores. You call sem_init, passing it the address of an int; later you call sem_destroy and pass it the same address. So the location of the int must not change.
Caveat, I haven't worked with Pin that much. Take this with a grain of salt.
My understanding is - you really shouldn't put copyable stuff in Pin. Period. If you want something like that wrap i32 in new struct and declare it !Unpin. Since negative traits are unstable, you just write PhantomPinned marker
I am only listening some of the concepts that I find harder to grasp, regardless of the context.
Macros are easier than GAT, maybe, there is a whole talk how serde goes crazy with macros.
PhantomData or Pin are hard to grasp, not as concept, rather to keep track when they are needed, many times it isn't clear that the way to fix some compilation error is to plug one of those types. A bit like having to do references to constant literals as well, another gotcha.
Usually when you want to give type some information it wouldn't normaly have. Like e.g. using it to store generic type signature for some kind of SQL builder. or to keep a lifetime that would otherwise be unbound.
> Pin
Just for self-referential structs. Since async relies on self-referential, it might also require it.
---
When to use it? When you either exhaust all other options or know enough to judge it worthwhile.
I've seen people picking up C++ from scratch recently and rest assured, that's not so obvious for a novice like it is for a person that's been using it for decades. I think the level of complexity is similar, but at least Rust features feel like they were designed on purpose.
C++ metaprogramming, to be fair, is a happy little accident that turned out being a blessing, more than something that was well designed from the start...
> Rust isn't really all that hard. I can write it as fast as I can write Python typically, so that friction you probably feel will go away...entirely.
I hear this a lot. Having written both, it seems to me that the only way to reasonably have this thought is that one never learned to use the repl in Python. This gives me a, say, 2-10x improvement in implementation speed, at least for exploratory-kind of coding.
"Is it possible to create a truly safe language without the dizzying complexity?"
Yes!
The good news is, they even already exist. There's even at least two variants of them: Pure immutable values (Haskell, Erlang) and languages that aren't pure functional but involve lots of little execution units that can't send mutable references between the units (Elixir, Pony) which when used even slightly properly makes unaccounted-for mutation a non-problem. Those are not intended as complete lists, either. I gather Clojure can be used to a large degree this way, for instance. Nor is this a full list of all the possible solutions, just the ones that leap to my mind.
The bad news is, the complexity in Rust is still there for a reason. I see the earlier efforts as something like the same picture as Rust is trying to be, but blurred. Immutability and inability to mutate across threads at all both solve the safety problems, but with a bigger hammer and a blunter flattening of the problem space. Technically, Rust's greater complexity allows it to hug the true parameters of the safety problem space more closely. I suspect that tradeoff is fundamental and you can't have Rust's feature set without roughly equivalent complexity.
Fortunately, not every language has to be Rust. Fortunately, Rust exists for those times when we really, really need it. There can't be one language to rule them all, and Rust isn't going to fill that nonexistent niche. That's OK. Rust only has to be the best Rust it can be, and those other languages can be the best languages they can be, and in the end that's just as good as it can get.
(Also, while this isn't a solution to the safety problem, you can still get a lot of mileage by adopting solutions from those domains into languages that technically unsafe, but work fairly well if you program them with a collection of reasonably safe patterns. I work a lot in Go. It is not particularly safe, technically; safer than C but that's a low, low bar. But in practice I don't have a lot of safety problems with it because I spent years cutting my teeth on Erlang and Haskell and my designs are informed by that.)
I don't think it's really possible to seriously consider Haskell as somehow less "dizzyingly complex" than rust unless you're already a math postgrad. I like haskell, don't get me wrong, but it is not in any way simple to write or read or work with by comparison. And while rust has some heady concepts in its design, for the most part even advanced users don't actually need to understand most of them to use it (I think this is actually rust's biggest innovation).
I do wish rust had borrowed syntactically more from haskell than C++, though.
Haskell is not really that complicated. What it is is capable of hosting dizzying complexity on top of its relatively simple core, which it is capable of hosting precisely because the core is so simple. It is similar to Lisp in that regard.
There's also rather a lot of spazzing out because some very simple concepts like "monad" in Haskell somehow managed to pick up a lot of very bad explanations and an aura of mystique they don't deserve. They're really only complicated if you go in assuming they must be, and that if you have a simple understanding it must be wrong because it has to be complicated. Rust fans may be familiar with the process; I see the borrow checking starting to pick up a bit of a similar mystique with the greater community, though I don't think it'll ever quite reach the same level of fever pitch as "monad" has. And I think there's even a similar process; start with Rust, print out Hello World, then immediately try to jump to writing a custom network application in Tokio and, yeah, the borrow checker is going to look pretty complicated since you're basically trying to swallow it all in one shot. The solution is, don't do that.
I detect some of that mystique in your post. It is a complete myth that you need to be a math postgrad to understand Haskell. It is a complete myth that you must understand group theory to use Haskell. It has even been observed that knowing too much Haskell can make group theory a bit harder to learn, and knowing too much group theory can make Haskell a bit harder to learn, because Haskell has a very programming-language centric view of group theory that means that even though some terminology is shared, everything is just a bit askew from the other. You no more need to be a math postgrad to use Haskell than you do to use a structured programming language, which also used to be a weird math nerd thing. Now it's just how programming languages work.
> I don't think it's really possible to seriously consider Haskell as somehow less "dizzyingly complex" than rust unless you're already a math postgrad.
This seems clearly false to me. Haskell has a higher abstraction ceiling than Rust, but unless you want to contribute to a particularly heavyweight library (`lens` comes to mind) you can get by just fine without going anywhere near it. In practice, most code in both languages maxes out at or below the mathematical sophistication of an introductory algebra class.
Aren't Haskell and Erlang both GC? I don't think anyone is touting Rust as newly immutable. The only differentiator (to my understanding) is Rust's safety without GC.
I don't know that that's really the whole story. I mean, C++ has smart pointers that do reference counting, same as Rust's do.
But C++ can't provide most of the rest of guarantees that Rust can, like that you didn't hold a reference to that argument, or return a pointer to a local variable that's about to go out of scope, or that these threads aren't both touching that thing at the same time.
That's true, a lot of Rust's features do exist in C++. I guess the main difference is that Rust is safe by default and you have to go out of your way to be unsafe. C++ is basically unsafe by default. In C++, the standard syntax for allocating memory gives you a completely unchecked pointer that you can freely use from whatever thread you want.
> or return a pointer to a local variable that's about to go out of scope
I thought of that recently. I wonder about using escape analysis to detect that and simply don't release the stack frame until the reference goes out of scope.
Static analysis/tracing absolutely can do a bunch of that kind of thing, particularly catching trivial cases.
But I think it's still a pretty important upgrade when those things are being checked by the compiler, for free, with guarantees, as part of every build, rather than using some side-tool that may be proprietary, deliver oodles of false positives, and so on.
What I mean is just fix that so it works. If you have split stacks the one for data doesn't have to track function call chains.
The advantage of using analysis tools is you only pay for that once. Not with every compile. And the tools are smarter than Rust. You also don't have safe vs unsafe code like you do with Rust.
You wouldn't just need split stacks for this, you'd need a spaghetti stack with some kind of heap allocation for frames. At which point perf goes out of the window already.
If you are using gcc, clang or vc++, to certain extent that is part of the compiler.
Just like C, following the UNIX principle, had compiler, assembler, linker, makefile, and linter since 1979. But people kept forgetting about the last piece of the puzzle and now it is a pain to bolt into existing code.
I answered the question about safety. You can have the safety of Rust without the complexity of Rust. I never promised there are languages that will have every last attribute of Rust but be simpler, and even typing that sentence out should make it clear that that is fairly unlikely to happen. Adding a GC is one way to simplify Rust. All simplifications of Rust are going to come with side effects. The complications are there for a reason; the question is always, does the code you're writing right now require that complexity, or can you use a simpler safe solution? Usually the answer will be the latter, honestly. Only a fraction of programs require that level of control... but it's a very important fraction!
There are some immutable languages inspired by Rust that are trying to have immutability without garbage collection but they're a ways off from practicality, I think.
I wonder too if we might see a world where a lot of application and business-logic code in Rust gets written essentially in the style of Python or Ruby— lots of immutability and copying stuff around because perf doesn't matter at that layer. But then the heavy processing part of the application can call down into vetted crates which do take advantage of Rust's more sophisticated features.
Am thinking here of the relationship between Python and Numpy/Pandas. Lots of companies use Numpy heavily but wouldn't dream of trying to hack on it or build such a thing themselves from scratch.
Similar story with places which do things like ban writing their own template code, but permit the use of std container classes.
I'm currently learning Rust (as a distinctly average programmer) and I think I agree. I put that so tentatively because I have experienced something of what Rust advocates claim - ie. when I've battled just trying to get a module to work at all for a while, but then when it does fit together (in a rather pleasing way, like gears snicking neatly together), it just continues to work. So I don't rule out the possibility that with more experience the fitting-together experiences will overtop the ugh-this-is-difficult ones, and I will become another Rust enthuser.
But on the other hand I'm often driven mad by the complexity, not so much of the language, but the libraries. There are several very popular & foundational ones I've used where I've literally not once managed to get anything working by following the incomprehensible docs, only by chasing up blog and forum posts in the desperate hope of finding a usable snippet.
Then there is the community, which despite its reputation for inclusiveness, is insufferably smug (for genuinely pleasant communities, try Elixir or Clojure). If you say something is difficult, they'll just inform you it's not, therefore by implication you must be thick or obtuse.
One example - clap, which is by far the most commonly recommended cli args parsing library. Plain uses of the derive api are simple enough, but I found it a huge time suck to figure out how to do anything remotely different from the docs' examples. On my first 'real' Rust project, it took nearly half my time just figuring out how to deal with cli args. In retrospect I probably should have either used the Builder api (which is more explicit) or parsed the args manually.
But I've experienced similar things repeatedly. Complexity seems to be tolerated (even vaunted) by the Rust dev community at large. I don't rule out that this may have good reasons behind it (the nature of Rust's natural domain, etc).
That's fair, Clap is an 800 pound gorilla of library that is trying to be heavy-duty and production-grade. But if you're just learning Rust and want to crank out a CLI for fun or education then I'd say that Clap is overkill and a distraction from learning the language itself. There are dramatically simpler libraries out there for argument parsing, e.g. https://crates.io/crates/lexopt . Heck, if I was just starting out and writing a thing for fun I might just forego a library altogether and manually parse arguments from std::env::args.
That was but one example of what I face at every single turn with Rust though. Complexity (not conceptual difficulty) seems to be the general culturally accepted norm. I truly don't rule out that this reasonable given Rust's domain of use. But there is a general reluctance from Rust advocates to accept that it's complex, and that this complexity makes it hard to use.
I do find the intense advocacy thing singularly odd with Rust. It's not like developers have to be convinced a thing is easy for them to use it. I'm still learning Rust (for reasons). But I don't feel the need to submit to social pressure from Rust advocates to feign ease.
In a way it's tricky to encapsulate, precisely because I do mean 'complexity' rather than 'conceptual difficulty', and complexity spreads through systems in thousands of small ways. Clap's one example - it's not only immensely hard to use (compared to what you find in other languages), but this is compounded by the fact that it's the library everyone recommends when asked online. This speaks to a cultural acceptance of complexity within the Rust community. I've found the same with nearly all of the commonly used libraries I've needed. As soon as I needed to do anything custom with serde it took me hours to figure out how. I gave up on all the general database libraries trying do some simple persistence to sqlite (though I did end up with rusqlite, which is genuinely simple). The utility traits smear complexity throughout just about everything an application programmer needs to do.
Without a lot of experience (not to mention vast amounts of memorisation), everything is just hard and slow to get done. And I do mean by comparison with other languages I've used, from Objective-C to Java, from Typescript to Elixir (and many others).
Again, this isn't necessarily a complaint about the complexity per se. It might be necessary (I don't feel I know enough to judge), given Rust's uses and constraints. But the community consistently and obviously denies the difficulty. I think I understand the sources of the denial. But that doesn't make it any less irritating!
Here's an example (of complexity or difficulty, or .. whatever your preferred defs/vocab) that arose for me today (but on any day I do any Rust programming or reading I could find a similar example).
In what language ecosystem other than Rust would that answer arise as an answer to a beginner's question? Haskell, or a research language perhaps. I can't make head or tail of the Rust Playground example there. In fact if I look through most of recent questions on the Rust forum, there's probably 1 in 10 where I even grok the question, let alone the answer. I haven't experienced this elsewhere. Rust has a culture of complexity, and its community is in a state of denial about it (for I think a fairly obvious reason). Furious denunciations ensue even if anyone dare suggest such a thing!
Interesting. I find the clap derive stuff great and haven't had any trouble figuring out how to use it. Compared to anything I've done before in any other language, it's so much simpler and straightforward for me. Maybe it helps that I used structopt before clap incorporated it? I don't know. Different people can have very different experiences.
I just recently started learning rust a few months ago, and I came to the same conclusion as you about packages. Cargo seems like the biggest impediment to Rust. GCC-Rust may put Rust on track to have a decent STL since (presumably) GCC will not import a package manager. However, insisting that a package manager have first-class treatment for a systems language just seems wrong.
On the other hand, the Rust STL itself is full of bit-rotting functions that have been stable for years but are still somehow only in the nightly release and haven't made it out of "experimental" status. I assume that the presence of Cargo in the ecosystem and the mentality that you can just use a crate to do what you want is a big part of this.
> GCC-Rust may put Rust on track to have a decent STL since (presumably) GCC will not import a package manager.
I think you may be misunderstanding something here? GCC-Rust would have no reason to import Cargo. Cargo is a tool for resolving and managing dependencies and generating compiler invocations. GCC-Rust is a compiler that would be invoked by Cargo.
> the Rust STL itself is full of bit-rotting functions that have been stable for years but are still somehow only in the nightly release and haven't made it out of "experimental" status.
Rust stabilizes several new library functions with every release. Which ones are you trying to use? Rust is driven by volunteers, and many functions only get stabilized when people ask for them and present concrete use cases.
* GCC-Rust would then provide an incentive to make the STL more complete, if users of Rust on GCC don't have easy access to Cargo.
* The first_entry and last_entry functions in BtreeMap come to mind immediately, but there were others I ran into. The fact that several new functions are stabilized with every release does not mean that many functions don't sit in the queue for years waiting for stabilization.
> if users of Rust on GCC don't have easy access to Cargo.
Can you clarify which users you mean? If you mean the developers of GCC-Rust, they don't need Cargo because GCC-Rust is written in C++. And users of GCC-Rust would have no problem using Cargo, assuming that GCC-Rust presents a CLI interface that's more-or-less compatible with rustc (which I assume they would want to do, because being compatible with the existing Cargo ecosystem is quite valuable).
> The first_entry and last_entry functions in BtreeMap come to mind immediately, but there were others I ran into.
The good news is that those functions were recently proposed for stabilization and the ten-day final comment period elapsed as of today with no objections, so these are on track to be available on stable as of Rust 1.66. As for the other functions that you allude to, I encourage you to join the libs-team Zulip and ask how best to proceed on a given API; quite often I find that there's nothing preventing an API from reaching stable other than finding someone willing to do the small amount of labor involved in documenting, writing a stabilization report, and issuing the stabilization PR.
People who use GCC to compile Rust code presumably will not use "cargo build" to build their programs, and presumably may not have "cargo" in their toolchains at all. They may have cmake or bazel instead, with manually-selected libraries. Perhaps you never envisioned that people would want to compile large rust programs without cargo being involved in the build flow?
Cargo, NPM, Pip, and other package managers present significant security weaknesses, and many users of C++ today do not work with this kind of flow because of this weakness. The code that Rust and C++ are aimed at is generally more security-critical than most javascript or python code.
Finally, it's great that those functions will finally be marked stable. They were stable as of 2019.
The problem I have with this isn't the specific functions, it's the process. Having functions that are stable, but waiting for years for someone to write a little bit of documentation about their stability is nuts.
I understand that people hate writing reports, but it seems that many maintainers are happy to churn out code for functions and then decide the project is finished before actually finishing it. Anyone else who wants the function is then in an awkward position: Do you want to step on someone's toes and write the stabilization report? Do you want to guarantee the stability of code that someone else wrote that you may not understand? You can't re-write it now - that would be wasteful and petty. So what do you do? You wait for someone else to take the risk.
In the Linux kernel, tons of documentation and reporting about your function being correct and stable are a prerequisite for submitting code, and this seems to work very well at preventing situations where you have lots of 90% finished projects. That is also true of most companies and most other commercially-relevant open-source projects.
But, conversely, why should someone take a risk of stabilizing a piece of fluff that nobody actually claims? It's not just that this wastes their time - those standards will be for us for decades to come. Just look at the mess that is POSIX by now.
In C++, the "STL" is the "standard template library", which was a large inspiration for the standard library, but not actually the same project. Due to this history, some folks still call the standard library "the STL" even though it's not 100% accurate.
Good news! Complexity isn't a baseline requirement: it's possible to make a language that has the basic patterns and benefits of an average Rust program. It's what we're doing in Vale [0].
It turns out, they key is to move some checks to run-time [1] to reduce complexity, move other checks instead to compile-time [2] where it doesn't increase complexity, and then make the borrow checker "opt-in" instead of forcing it to be used everywhere [3].
Adding generational references needs to be built into the language at a very deep level to work properly, AFAICT.
One can almost do it by using "side tables" but it would come at a rather high performance cost for Rust. Since generations are located inside / next to the object in Vale, we can take advantage of the cache to avoid any significant slowdowns.
Additionally, one of the main benefits of this approach is simplicity for the user, and I'm not sure Rust can be made simpler by adding more mechanisms.
That's perfectly fine. However, without Rust, we cannot get there from here.
Say what you will, but Rust is the first language to crack the Linux kernel monolanguage (I don't see Ada in the Linux kernel, for example). That's a good thing as the tooling will then be in place so that RustEasier(tm) doesn't have to go through such a slog to get used in the kernel.
People forget that we went through a similar process with Linux on Alpha. The Alpha port cleaned up a lot of the x86-isms that allowed Linux to be ported to other architectures far more easily afterward.
I feel the same way. I've dove in three or four times on Rust, but I get stuck learning Rust and not producing what I first set out to do in it.
Currently, I have committed to learning Zig for my low-level or C-type projects, and I have been having fun.
When I need what Rust claims to offer, I drop back to SPARK2014. A formally defined programming language with a set of verification tools. It meets all high integrity standards, and it has been around for a while with some helicopter operational code and the NATS iFACTS air traffic control system with over 500k lines of SPARK code.
It's nice to see now that AdaCore is teaming up with Ferrous Systems[0] to try and bring some of this to Rust. It shows the Ada/SPARK community want to propagate high-integrity software and not just promote a PL. As much as I like terse and expressive languages like J or APL or Haskell even, I find SPARK2014 to be more writable, readable, and it covers Rust's domain, even embedded [1].
It's Pascal-like block structure with 'end' delineates the program structure nicely for me, and in my opinion, much easier to follow than Rust. Elixir has sort of won the BEAM PL of choice category (it has 'end' blocks too). I think Rust will only get more complex. A SPARK on BEAM would be something!
Did you bother to read the OP? The thesis is that Rust is actively reducing its own complexity by becoming more self-consistent over time, with fewer arbitrary restrictions about what can be used where.
It's really not that hard. You just need to practice a bit and get over some concepts. Highly recommend buying a rust book to supplement the online rust book, or getting involving the a forum/chatroom and asking questions. The community realizes it's a bit of a brain shock, but once you get over it, it's actually pretty easy to do 99% of the things you need to do. Even, easier than in other languages in a lot of cases. A few areas are tricky/hard, but if you walk before you run (which most people SHOULD do in other languages) it's entirely doable.
The problem with an easier rust is... These are the rules for using your computer safely. Like, this is what you should be doing when reading/writing C code. Or C++. So learning rust is hard, but it makes you a better programmer faster IMO. Also... once you write rust code it's usually good, no weird surprises Saturday at 2:00am in production, it's just done and works.
Anyway wishing you the best, but be careful asking for "an easier rust". An easier "Rust" probably isn't safe and you might as well just be writing any other language.
It has Rust-style ownership to guarantee memory safety, but without the complexity of lifetimes.
There is still a borrow-checker-like component, but I believe it should be much simpler than Rust's (see e.g. "method bundles" and how they are called).
Still in the research phase. It is an open question how well this approach scales.
I'm just a hobby coder. Except for lifetimes and a bit of fighting with the borrow checker, the language came fairly easily for me. After banging my head against the borrow checker for a week or two at some point something switched in my brain and all of a sudden I wasn't fighting the borrow checker anymore. This was spending maybe 1-2 hours a day with rust. I don't often have a lot of time to write code. Everyone has their own experiences, though, and this is just mine.
> My theory is that every technology that is too complex gets replaced with something that does the same thing more simply.
If what you said was true, JavaScript itself should have been replaced by something simpler, which didn't happen. My theory is that when some technology is good enough, it gets stuck. The previous JavaScript tools got replaced because they were not good enough.
The main advantage of rust is that you can “do anything” with it. It’s fast, it has deterministic performance, you can embed it, you can write low level code, you can write fancy high level code.
Cpp devs never moved because there were always large projects chugging along in cpp and they always had the feeling that they knew what they were doing.
Rust is rather lean - you'd have to give up something to simply. e.g. most simpler languages are GC languages. That said, GC shouldn't be a problem for 90% of apps.
Now, it's not unlikely that eventually Rust will accrete enough baggage to need replacement too - I'm guessing we have 20 years minimum until that happens?
I'm not entirely sure what this is referring to? If it's something about Rust's postfix await syntax, it's quite lovely in practice, and even if someone disagreed they wouldn't leave the language over something like that.
I think any language that has the concept of storage classes will be complicated because you have to deal with copy versus move vs borrow semantics. Other languages get around this by just saying that primitives are on the frame and everything else is on the heap.
The counter-observation might be that Rust exposes, as a responsibility of the programmer, the completion of what would otherwise be compile-time analyses in sophisticated compilers. (Borrow and move look what SSA could comment on, for a simple example.)
Worth noting that bjz is a compiler hacker, and this is a list of "languages" from a language implementor's perspective. From the perspective of a user of the language, nearly all of these are just "Rust, the language"; to a user there's no such thing as e.g. a "trait language" or an "attribute language", there's just Rust, which has traits and attributes in the same way that JavaScript has objects and closures without having an "object language" or "closure language". Of the things that can be described as actual languages there, Unsafe Rust is just a minimal superset of Safe Rust, whereas Const Rust is just a subset of Safe Rust; it's not three languages to learn, it's one language where a handful of things are either available or not in certain contexts. The one thing that legitimately is its own language is the macro-rules language, which is a DSL for syntax manipulation.
Not quite sure what you mean by sublanguages - procedural and declarative macros perhaps?
In any case I wonder about the simplicity/complexity dance. Simplification ends up with something like Lisp, in which you write DSLs that each has to be independently learned. Elixir does something similar. Some differentiation might just be a natural consequence of chasing ergonomics.
If I had to guess: Safe Rust, Unsafe Rust, proc macros, declarative macros, the attribute language, and maybe async Rust (although I'd really just consider this sugar on Safe Rust).
I love Rust, but I would love to see more syntactic unification between these components. Both macro languages, in particular.
As far as I'm concerned any programming language either has a good, builtin, type safe macro system, or people are going to use code generation (on large complex projects). And I never want to have to work on something with custom code generation.
Typescript seems to have embraced the third option, which is having such a batshit insanely expressive dynamic type system that you don't need either 99% of the time while still having proper type safety.
Unsafe Rust is a minimal superset of Safe Rust, proc macros aren't their own language, there's no such distinction between Rust and "the attribute language", and async Rust isn't its own distinct thing, it's all just Rust.
Unsafe Rust is, in the Rust Language book's own words, a "second hidden language" within Safe Rust[1]. You're absolutely right that it's a superset; that's what makes it distinct and therefore a unique language with unique semantics (even if the syntax is nearly identical).
Procedural macros can be used to define new languages within Rust, which means that grokking them requires the developer to understand their expressive capacity. For example, the paste[2] crate introduces a little bit of novel syntax for combining identifiers.
Attributes are the counterpart to Unsafe Rust: their syntax is described separately from the rest of Rust[3]. Understanding how to use them involves components of Rust that aren't tied to the syntax of programs (e.g. doc and feature attributes, which connect to Rust's standard tooling instead).
I agree that async Rust isn't its own distinct thing. I threw that one in as a possible interpretation, to make the count work.
> that's what makes it distinct and therefore a unique language with unique semantics
Unsafe Rust doesn't have different semantics from Safe Rust, it's the exact same language but it allows more operations. Which is to say, for any given expression that compiles with Safe Rust, if you wrap that expression in an unsafe block it will have precisely the same semantics. For the book to describe it as a "second, hidden language" is being a bit fanciful on its part; it later goes on to clarify that it just gives you a handful of extra powers.
> Procedural macros can be used to define new languages within Rust
Sure, but by that logic, Rust (and every other language that supports DSLs) has infinite languages inside of it, which does not amount to a particularly useful distinction; either a language supports crafting DSLs or it doesn't. To suggest that this means that the language itself has infinite sublanguages obscures its actual sublanguages, such as the built-in macro-rules DSL.
> Attributes are the counterpart to Unsafe Rust: their syntax is described separately from the rest of Rust
This is a misunderstanding of the reference manual (which is itself non-normative and fairly ad-hoc in structure); most pages contain their own grammar specification, e.g. the syntax for items is described at https://doc.rust-lang.org/reference/items.html , but there is no user-facing item sublanguage in the same way that there is no user-facing attribute sublanguage. Attributes are attributes, they're just a part of Rust.
> Unsafe Rust doesn't have different semantics from Safe Rust, it's the exact same language but it allows more operations.
"Allows more operations" seems like a straightforward meaning for "different semantics" to me. In Safe Rust, you can't access union members. In Unsafe Rust you can.
I agree that it's a little fanciful, but I also don't think it's really wrong.
> To suggest that this means that the language itself has infinite sublanguages obscures its actual sublanguages, such as the built-in macro-rules DSL.
In many languages, the macro language is either homoiconic or nearly homoiconic. In Crystal, for example, the macro DSL looks like normal Crystal, but with a few extra sigils. You can't invent new syntax in it.
When we talk about understanding C we generally take that to include the C preprocessor, despite the latter being a conceptually separate macro language. I think the same standard (except much, much better) applies to procedural macros in Rust: you need to understand a separate set of token production and consumption rules that interact but do not align with Rust's core syntax.
> This is a misunderstanding of the reference manual
I wasn't referring to the production rules themselves; I'm aware they're on most pages. I was referring to the fact that the attribute language is explicitly taken directly from a separate language (C#).
> "Allows more operations" seems like a straightforward meaning for "different semantics" to me. In Safe Rust, you can't access union members. In Unsafe Rust you can
The semantics are the same. The meaning of the operation is same. In both safe and unsafe accessing union members is the same operation with the same meaning. The only difference is that in unsafe Rust you are allowed do this. This is a difference in permission, not meaning
Unsafe Rust is just Rust, but there's a few extra things you can do inside unsafe blocks that you can't normally do outside them (because they're unsafe). I wouldn't call it a different language.
See my response to 'kibwen. We can nitpick all day about whether it's really a separate language; I think it satisfies at least one important definition of "language" by having its own separate syntax and semantics that aren't accessible to Safe Rust.
But by that argument every feature in a language is it's own separate language. "This is the language that can only be written inside function definitions", "This is the language that can only be written inside ternary operators" and so on.
The list I gave was the ones I've heard people in the Rust community, including official Rust language documents, refer to as separate languages. Does that guarantee that we're carving the nature of "computer language" at its joints? No; it's merely a shorthand for expressing the range of syntaxes and semantics that you need to know to "fully" know Rust.
It's the same syntax. There are no special operators in unsafe rust. All unsafe code is syntactically valid safe code. The reason it will not compile it because certain operations are not permitted, not because of syntax errors
These two are the exact same language. Some features (like calling unsafe functions, or dereferencing raw pointers) are "locked" outside unsafe blocks, but syntactically and semantically they are identical. It's easy to show this: put a unsafe block around a normal "safe" block, and all that happens is an extra "this does not need to be an unsafe block" warning from the compiler.
My other responses say this in more detail, but: they're not the exact same language if they have separate semantics and syntaxes, which they do. The fact that they can be composed together (much like C and the C preprocessor) does not make them the same language; it just makes them compatible.
There's something wrong if a language is so unsuited to programming itself that it needs another language - or five more languages - to get the job done.
This is one of the great things about JavaScript - it's flexible enough to do everything in its ecosystem.
I don't think that's a good metric. Most of the most successful programming languages of all time have had at least one macro or attribute language attached to them.
The funny thing is Rust adds a lot of complexity to solve the main problem it purports to solve: memory management. I spent the first 20 years of my career doing C/C++ development and we were able to "solve" that problem with far less complexity. Rust changed so much in the early days (I really hate the mantra of move fast and break things when it comes to programming languages) that I got tired of all the breaking changes and total disregard for backward compatibility. Which is cool if you're into that sort of thing, but I'm not. Maybe Rust has added more killer features since then warranting the complexity? I really don't know.
I'd be interested in hearing are there other reasons for learning Rust other than just memory management? What other problems is Rust solving?
Sorry - got busy over the weekend. In the five different places I worked while using C++ the problem was solved with convention and discipline. When MFC came out it helped standardize memory management best practices and people began following them. Also, auto_ptr<> was a godsend!
After my initial failed attempt to learn Rust I then took a second shot at it by writing Actix web code and had alot more success. This is the path I would take if I was returning to Rust.
Having said that, I felt that for the most part this approach was just avoiding the task of learning the complexities. But, I do believe that learning something hard starts by learning a little bit of it and building on that initial foothold.
Exactly this, if you are building some CRUD app then it's as easy as using Java or Go etc, because basically there you have request -> contact some db -> return result, maybe do some processing of that data, so no need to fight borrow checker at all. But if you build something like multiplayer game server with lots of state of 3D world that constantly changes and interacts with itself, that is operating on self referencing data structures etc then it's not as easy as in GC language.
The moment you need to do something remarkably simple, such as string concatenation, you are confronted with the reality that Rust is a much lower level language.
Part of my work involves the braid HTTP spec. Braid uses a custom server-sent events like protocol sent over long lived HTTP connections. I spent about a week trying to get this working using rust & actix. My code became mired in complex lifetimes, crazy iterators, Pin, async and Futures. Hundreds of lines of code later, I gave up and rewrote the whole thing in an hour or two, in about 30 lines of javascript.
The whole experience was so bad I've sworn off using async rust entirely until a few more features land. Thankfully, apparently there GAT lands next release - although its not clear to me if it'll work for futures yet, or if thats a later goal.
This is what Rust is ... the simpler, safer alternative to C++.
It is possible that, for the demanding domain spaces that have historically required C++, Rust is the end of the line for how far you can dumb down a language and still have the required sophistication and performance.
> The biggest problem of Ada IMO is that it was always supposed to be about safety, but it never addressed the most common source of bugs: pointer safety. Not even with SPARK. It's like discussing about how can you fortify the door in a house with three walls. I suspect they planned to solve it like everyone else (with tracing GC), but that never materialised (because most Ads users don't want tracing GC) thus was always kinda weird “safe” language which doesn't tackle the most common source of bugs.
> Finally, in year 2020, it solved that problem. By picking ideas from Rust, of course.
> But by that time momentum was lost and it would be very hard to overcome that “safe language without safety” stigma.
Yep. Borrowing ideas is good and completely ok, as Rust also does copiously, this is how our languages improve. But of course Ada has a lot of other safety features that Rust does not.
I am personally still doubtful of Rust's adoption. I consider the 2008-2014 period the peak hype cycle for Haskell and don't see that it changed professional programming much. I predict it will be similar for Rust.
Now I'd be really interested in some examples (names/years) of your simplification of JS tools? theory, because as a semi-interested bystander it doesn't match my experience.
C arguably is, considering the analyses done on programs in C are not precluded from being super stringent, perhaps as stringent with compiler-specific flags?
Every time there's a new post about 'big new features' in current or future Rust releases there's someone shouting in fear of how Rust is getting more and more complex. Rust may be complex, but 99% of newly added features have not made it more complex than the initial release.
Consider a different language with three orthogonal features. You could think of its 'design space' as a cube, where each feature corresponds to a dimension, and where code that uses some features are a point in the space. If the implementors of this hypothetical language didn't know exactly how all three features should interact together but they could figure out how pairs of features would work, you could imagine their initial implementation as the same cube with a chunk missing out of the corner. Later, after users and implementors gather experience about how the language feels, they make a big proposal: The next release will be a full cube instead of a cube with a corner missing! Wow! Now the big question: did this release make the language "more complex"? Well, it is more capable. But it doesn't add any new orthogonal concepts, it just filled out the full space that was implied by the original three. And arguably "cube" is simpler than "cube with a corner missing".
The design of Rust is the cartesian product of about five primary orthogonal features on top of some basic concepts that might be familiar to C programmers. Maybe they are: lifetimes, generics, traits, enums, unsafe. (Argue away if you choose a different five.) When these features were chosen all the way back in 2015 the designers did their best to consider what might happen if you tried to use various combinations of them at once; but crucially, large portions of the corners of this 'space' were left unimplemented simply because it was too hard to do all at once [1]. 65 releases later, Rust has added very few new features -- as in orthogonal concepts -- [2] but has made a lot of progress filling in bits of the design space that was unimplemented but implied by the original five.
And that's literally what this whole article is about: Niko's vision for filling in Rust's implied design space that was left unimplemented for practical reasons, "Making Rust feel simpler by making it more uniform".
[1]: At some point after you've laid the foundation for a big idea it's better to let your idea tell you how it wants to work instead of forcing it, and that requires time and experience with it.
Maybe this will be an unpopular opinion here, but I guess that's one of the reasons why Go is easier to use than Rust. Ok, the main reason is of course that you don't have to think about memory management complexities. And Go also has fewer of these "orthogonal features" than Rust. But for those features that are there, they made sure that, as far as possible, every feature was working together with every other feature from the start, and not "we'll start by allowing this only for A, B and C, and then let's see where it goes from there"...
async drop is exciting because it means you can use the RAII[1] pattern for arbitrary resources. I've used this to, for example, manage infrastructure spun up for integration tests. I spin up a Docker container at some point in the test, assign the result to a guard variable, and then use RAII to clean it up when the variable goes out of scope.
Making it work without async drop was pretty hacky, so I'm looking forward to removing those hacks. Well done, Rust team!
I'm a software developer with over four decades of experience under my belt in every environment you can imagine - startups, manufacturing company, utility, retail, banking, education - and here's a trend I've noticed in that time: many languages come, a few stick, and they're slow to fade away. Very few have stuck around for decades. Today's darling is tomorrow's pariah. Cobol, C/C++, and Java are great examples of that.
Where is Rust? It's behind Cobol on the TIOBE index. That puts things into perspective. I remember when C took the world by storm in the early 80's. That was a result of severely underpowered 8 bit and 16 bit computer where your only alternative for getting any performance was to write in assembly. C's ability to be used as a high-level assembler were its killer feature.
Java had a similar ascendancy in the 90's. There were several widely-available CPU architectures that were popular at the time and it's ability to write-once, run everywhere with acceptable performance was its killer feature. It certainly didn't hurt that it borrowed heavily from C's syntax.
What is Rust's killer feature that will compel widespread adoption? The borrow checker? I don't think so, and the market doesn't appear to think so either. To wit, both C and Java enjoyed a meteoric rise within five years of their release to the public. Meanwhile Rust is nearly eight years old and yet still is lagging behind Cobol. I don't see the situation being much different two years from now when Rust is 10 years old.
I have a couple of questions then. Why do you think Rust will ever experience widespread adoption? Assuming it won't (which appears to be the case), for what programming communities would you expect for Rust to have widespread adoption?
According to TIOBE, C has lost 60% of its popularity between 2015 and 2017, and then doubled next year. What's more likely: that a 40-year-old language C had such a massive sudden swing, or that TIOBE data is garbage and measures search engine's algorithms, not language relevance?
I don't see the 60% drop in popularity between 2015 and 2017 as you claim (see https://www.tiobe.com/tiobe-index). C has never not been in the top 5 languages used since the index was created 35 years ago. There have been numerous cycles where it's been #1. There's been very little volatility in the top 10 for the past 10 years, as opposed to the 25-50 ranked languages where Rust resides which has considerably more volatility in their rankings. That's why a lot of people consider anything beyond the top 25 to be just noise.
TypeScript is the 8th most popular language according to Redmonk. TIOBE has issues even in the top 10, and +/- 60% errors bars on their top 2 language!
It's not a problem of noisy data for the long tail languages. It's just wrong data. It's a poor unstable proxy (subject to proprietary query interpretation and anti-spam algorithms that vary over time, query terms don't reflect colloquial names, and name mentions aren't usage), and it's a misrepresentation of the data (searches include historical documents, which is not a reflection of current usage). It's all noise, no signal.
I broadly agree with you that there are many fads in the industry that are touted as the "next big thing" only to fade into obscurity a decade later. But I don't think Rust is one of those, and the reason doesn't even have anything to do with what the language can or cannot do.
No, it's because all the big players in the industry are very involved. So, on one hand, it's not one company's pet project, like Java was - and yet it's big enough that "no-one ever got fired for buying IBM" kind of reasoning is already starting to show up. And I don't think anyone is likely to back out at this point given the effort already spent. For better or worse, this is what we've bet the industry on.
(To be clear, I think the language is actually great by itself, and it's good to see something striving to be safe-by-default to fill that spot in the industry. But let's not fool ourselves when it comes to programming languages winning on some abstract merits, outside of the broader economic context.)
As a library author a big aha moment for me was being able to write proc macros. There are so many creative (and simple to implement) ways of getting around certain limitations when you can just do some smart code-gen. I think a lot of people get overwhelmed by the limitations before considering what the world of proc macros has to offer.
Was it simple and elegant from the beginning, or did it click at some point? I started learning it from the O'Reily book yesterday, and so far it's easily the steepest learning curve for a language.
In my honest opinion, lifetimes should be hand waved until you've played with the rest of the language. By then it'll follow as naturally as your first lesson on OOP.
I wouldn't recommend the O'Reilly book for a beginner, use the official book in the docs, and do Rustlings alongside that. That's how I learned. Rustlings especially smooths you into the concepts while the O'Reilly book starts off writing a Mandelbrot generator as the first project, which put me off of that.
In my opinion, books are the worst way to learn a programming language. Reading existing codebases and watching tutorials is usually far more effective, especially when you can mostly transfer knowledge of how to do things in one language to another.
Because unsafe Rust has at least as many footguns, and a narrower happy path (second-class support for shared mutability, raw pointers are a pain to use and encapsulating in a handle struct might help though I haven't tried writing code this way, Pin violates SB, Box may or may not disable aliasing raw pointers), than C++.
Your comment has nothing to do with the blog post, which is about when rust features can be used (in combination with each other).
> Up until now, Rust has had a lot of nice features, but they only work sometimes. By the time 2024 rolls around, they’re going to work everywhere that you want to use them, and I think that’s going to make a big difference in how Rust feels.
No matter how you think your language will be whatsoever, it is just your crowd. For the rest of the world, it does not matter at all. It is just a tool, if I need I will use it otherwise I won’t spend a single minute to look at it.
Also if you think memory safety is the hardest problem in programming, you probably haven’t written any non-trivial program anyway.
This is why Rust will eventually become the next C++ - it's becoming just as bloated with type theory, a cumbersome syntax, and will eventually ignite a begrudging dislike by the people that have to find a subset of the language to make sense of just to write simple programs. Everything about this language is overcomplicated by language enthusiasts.
The best thing for Rust over the next few years is for it to fall to the wayside while saner approaches like Hare and safer C syntaxes take center stage.
As someone who generally prefers high level languages (Ruby, Python, JS) I'm enjoying Rust because unlike C and Cpp, I don't really have to care about memory. I don't want to spend my time thinking about memory allocations and deallocations.
Rust lets me focus on logic like a high level language does and the compiler tells me whether what I'm doing is memory safe or not. I'm vaguely aware of copying/cloning values, but Rust is pretty performant out of the box and I don't end up doing it very often.
For the shitty, first pass prototype code I've written so far I've seen a ~30-40x speedup compared to the Python implementation and a ~2x speedup compared to a Go implementation written by someone else: https://github.com/icza/s2prot.
That is why I chose Rust over a GC language like Go (E: I also wanted to learn it ofc). It's a lot faster out of the box, even without me having a strong understanding of memory operations.
I don't get it, where's the bloat here? By using consistent rules across the language, Rust is requiring developers to keep less in their head. The author acknowledges this:
> Newer users find Rust easier to use and more consistent; they don’t have to learn the “edges” of where one thing works and where it doesn’t.
E.g. currently, Rust users have to know that if they want to use async on a trait, they need to add a dependency to the async_trait crate. Soon they won't.
I have primarily worked on kernels written in C during my career, but I read through the official Rust book and wrote a small microkernel using Rust and my opinion is that Rust is already everything I hate about C++. It's already a huge language with a large upfront learning curve and there's no hope of mastering it as it's already grown so large that it could never fit in one person's head. Instead of focusing on solving my task I'm often thinking about how to deal with the language itself - a hallmark of large languages that promise the world if you just takes days learning about higher kinded type systems.
C has innumerable problems, but at its root it's a simple language that is easy to learn, and it's been fabulously successful because it's small and *doesn't* try to abstract too much over the architecture you're working on. My feeling is that Rust is being pushed by people who write application level code because they heard that C was bad and programmers are simply enthralled by overcomplicated programming languages because it gives them something to read about.
I agree that Rust is a large language, but I think you're overstating the complexity: it's been my experience that you don't need to know all (or even most) of the "clever" stuff to write large, performant Rust programs.
It's generally true that you can spin engineers up quickly on C, because it looks like a simple language. But that's because C translates static program properties into dynamic ones, and expects engineers to spend years of their careers chasing down the same handful of bug classes that Rust eliminates outright. In other words: C externalizes the learning curve (and makes engineers pay for it in blood and tears).
> C has innumerable problems, but at its root it's a simple language that is easy to learn, and it's been fabulously successful because it's small and doesn't try to abstract too much over the architecture you're working on.
Except for the C abstract machine, which bears no particular resemblance to 99.9% of the machines that C programs run on. Every kernel that I'm aware of is stuffed with nonstandard code and nasty hacks to keep optimizing compilers from correctly (per C abstract semantics) optimizing out code.
> I agree that Rust is a large language, but I think you're overstating the complexity: it's been my experience that you don't need to know all (or even most) of the "clever" stuff to write large, performant Rust programs.
The same could equally well be said for, say, scala. In my experience though, you always pay one way or another for the unneeded and unwanted features in a language. Unreasonable compile times that kill joy and productivity are just one of the ways.
I've heard this argument many times from Rust enthusiasts, most of whom do not work in C, C++, or even write system level software. Of the kernels that I have worked on, the Linux kernel core is very high quality C (even if many of the drivers are not), and other large parts of the stack like grub and systemd are also written in C and yet the sky isn't falling for users around the globe (and on other planets).
The Linux kernel has a constant stream of fairly severe, exploitable bugs caused by memory safety errors, not to mention non-exploitable bugs that lead to system instability or even data loss.
Just a few months back there was a bug that allowed any completely unprivileged user to elevate straight to root using a combination of unprivileged user namespaces and cgroups. Other than RCE or severe data loss, I would say this is one of the most severe forms of bugs possible in a kernel. For some period of time the kernel's basic security features were completely ineffective, and this is possibly one of the most widely used kernels in the world!
Severe security related bugs are really common with our OS's and user space software, and we just pretend that it's not a problem. When your kernel can't even offer basic security isolation between users in a reliable manner, when visiting a malformed website with your browser results in arbitrary code execution, or when a malformed message sent to you phone can completely own it, I would say there is a serious problem.
I can't speak for that group. I've been doing systems programming in C for about a decade now, and C++ for about half a decade; that includes both kernelspace programming and compiler development.
I'm not claiming that the Linux kernel core isn't high quality (or is, for that matter). I'm claiming that the kernel maintainers increasingly have to resort to all kinds of extensions and tricks to keep optimizing compilers from simply deleting their code, because nothing about C's semantics reflect the "bare metal."
The Linux kernel core is the product of decades of effort by thousands of people, many of them world-class experts in the relevant domains. Very, very few software projects receive anything close to that.
No one is claiming that writing safe C code is impossible; the problem is that writing unsafe C code is easy.
And the Linux kernel is introducing Rust code [0] due to the fact that even Linus Torvalds acknowledges that Rust solves issues that would be prevalent in C code.
Overall I don't think Rust is a silver bullet, but it works effectively as a high level manual memory management language. Whereas when I had to work on C++ code I was running into messes where lifetime issues were undefined behavior instead of compiler errors
I'm curious why you think it's too big to master. There's certainly a lot to the language, but it's design means that these various facets are largely contained to their area of effect. I'd be confident in the assertion that most rust developers don't need to understand much more than the basics of the type system, traits, lifetimes, and the surface levels of async.
The biggest benefit to the language is precisely that all the Lego pieces fit together and you simply don't need to worry about (in Lego parlance) "illegal moves". Whenever I write C or C++, I'm constantly worrying about whether or not I'm doing something incorrectly and potentially writing dangerous code. In rust, this mostly disappears and you can focus on the actual problem domain you're working in rather than computer science problems.
> I'm curious why you think it's too big to master.
Many of us do find Rust very hard to get to grips with, however much we're informed by Rust advocates that it isn't! I don't know if there's any readily available way to be objective about this? I think all we have is attestation. Here's Chris Keathley (of some Elixir fame):
> I've written a non-trivial amount of Rust code (50-100k lines) .. and I feel like I barely understand Rust as a language
It's entirely reasonable to have trouble getting to grips with Rust, as ownership and borrowing are quite different paradigms compared to the usual mainstream languages that people tend to know. But there's no way to write "50k lines" of Rust without saying that you understand the language, so I'm baffled by what the author is trying to imply there. If you don't understand the language, then you won't even make it to 1k lines.
I didn't find anything tricky about the ownership model. The last two languages I learned were Clojure then Elixir, both of which had far more concepts novel to me (with no experience in Lisps nor functional languages), yet neither gave me the trouble Rust has.
Frankly your response is typical of the smugness I find with Rust advocates. You 'know' Rust isn't complex (perhaps because of your own experience). When people say they find it so, you consider it your role to inform them about how they are mistaken about their own experience.
It already is the next C++. It's a powerful language full of incredible abstractions but sadly that attracts incredibly smart developers who worship at the altar of complexity.
I enjoy rust for what it is, and I've had fun writing programs in it. You couldn't pay me to work with it in a professional capacity involving peers. I'll stick to Go, where my code reads and writes like everyone else's.
But Go can’t be used for all low level programming. Of course a GCed language is easier to read, it’s simpler at the cost of performance.
Rust complexity is more of a debate about low level programming below Go. Where only C/C++ and Rust exist. Can there be a simpler language in that space?
Maybe not. The problem is that you have to have a language powerful enough to do all the things, even the things that haven't been thought of yet. That means, basically, that you have to be able to manipulate raw memory. You can't depend on a library or a language built-in, because you have to be able to write the library, and do things for which the builtin doesn't exist yet.
And then you have the problem of not blowing yourself up with that power. That comes with 1) all the danger of C, 2) all the complexity of C++, or 3) all the difficulty of the borrow checker in Rust. If a fourth language appears with the necessary power, it's going to have equivalent drawbacks.
There are far more languages in that space than just C and Rust. Zig is one of the more recent examples, but e.g. Ada is also around, as is Delphi and many others.
This must be the fate of Rust - eventually it will be replaced with a language that gives the memory and thread safety without the melt-your-brain complexity.
I want a simpler version of Rust.
It's too hard.
Is it possible to create a truly safe language without the dizzying complexity? Or is it the complexity a baseline requirement?
I wonder if there is a subset of Rust that can be extracted someone into a new, minimal language with the core benefits.