Rust 2024 the Year of Everywhere?
smallcultfollowing.com
smallcultfollowing.com
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.
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
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.
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.
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.
There are lots of ways to shape your solution.
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.
You don't find it hard. Many do.
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.
I'll agree with your other things, but discouraging global state is an aspect that reduces ultimate complexity, rather than increasing it. :P
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.).
The crate ecosystem (oncecell, lazystatic) lets you completely sidestep this limitation for now anyway before better const behavior eventually gets added to stable
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.
In Rust same thing holds, macros are easier than GAT
I don't get why PhantomData or Pin are that hard to grasp?
PhantomData is just a type marker that has no purpose other than help compiler. See https://doc.rust-lang.org/nomicon/phantom-data.html
Pin is a way to create self referential structs, by pinning memory and preventing moves.
PhantomPinned is a marker type to ensure your type is not Unpin (it is by default). Unpin it thing that shouldn't be pinned, like bool, i32, etc.
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.
In C++ you would delete the move constructor:
struct sema {
int v;
void sema(sema &&) = delete;
};
In Rust you might try this, but it doesn't work: struct Sema {
v: Pin<i32>,
}
there is no way to create a "Pin<i32>". The closest you can get is probably to malloc it: struct Sema {
v: Pin<Box<i32>>
}
but now Pin is adding nothing at all (except perhaps a hint to the reader) and may be deleted. The real guarantee is provided by Box, not Pin.It looks to me like Pin is created to support async, and outside of that use case is not very useful, and is a poor replacement for non-movable types.
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
struct NonPinI32 {
v: i32,
non_pin: PhantomPinned,
}While you’re right that Box gives you a stable address, you can still move out of a Box, or swap something into the Box. Pin<Box<P>> prevents that.
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.
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...
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.
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 do wish rust had borrowed syntactically more from haskell than C++, though.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
Can you give an example of what you mean by "complexity"? I think this could be an instance of people simply having different definitions.
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!
This is an answer on the forums to a fairly beginnery-looking question: https://users.rust-lang.org/t/what-does-borrow-mean/81794/3
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!
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.
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.
* 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.
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.
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.
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].
[1] https://verdagon.dev/blog/generational-references
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.
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!
[1] https://blog.adacore.com/how-to-prevent-drone-crashes-using-...
[0] https://blog.adacore.com/adacore-and-ferrous-systems-joining...
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.
That is basically what Val aims to do:
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.
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.
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.
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?
Efficiency has its price.
I find it hard to believe that there's no way to get the benefits of Rust without 5 languages.
Why can't a programming language be one language that does everything it needs to do? Seems crazy to me to have 5 or 6 sublanguages.
https://gist.github.com/brendanzab/d41c3ae485d66c07178749eae...
In which there is:
>Many sub-languages to learn, many with different syntaxes and semantics. For example:
> the expression language
> unsafe runtime language
> safe runtime language
> compile time language
> the type language
> the trait language
> the macro language
> the attribute language
And was discussed (122 comments):
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.
I love Rust, but I would love to see more syntactic unification between these components. Both macro languages, in particular.
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.
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.
[1]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
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.
"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#).
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
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.
[1]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
It just allows access to: raw pointers, unsafe fields and methods, and unsafe traits.
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.
This is one of the great things about JavaScript - it's flexible enough to do everything in its ecosystem.
Where have you heard this? That's certainly not the case.
Some relevant reading: https://without.boats/blog/revisiting-a-smaller-rust/
I'd be interested in hearing are there other reasons for learning Rust other than just memory management? What other problems is Rust solving?
Entirely depends on what you're building.
Edit: You can have a small Actix app up over the weekend and never touch "lifetimes" until you're ready.
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.
https://play.rust-lang.org/?version=stable&mode=debug&editio...
I usually use concat() though.
https://play.rust-lang.org/?version=stable&mode=debug&editio...
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.
I love rust. But, wow.
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.
In Rust the complexity comes from elsewhere (eg rejecting GC), there are lots of easier languages that match the safety.
Rust is not really the paragon of safety, its guarantees are similar to easy languages like F# or even Java. And behind Ada.
https://lwn.net/Articles/908057/
> 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.
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?
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.
[2]: async, and that's it?
Making it work without async drop was pretty hacky, so I'm looking forward to removing those hacks. Well done, Rust team!
[1] https://en.wikipedia.org/wiki/Resource_acquisition_is_initia...
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?
https://blog.nindalf.com/posts/stop-citing-tiobe/
BTW: According to the TIOBE horoscope Rust is way ahead of TypeScript and Bash.
C# has jumped from 0.77% in Mar 2020 to 4.75% in Apr 2022. One month, 6x increase.
Even if you just take relative rankings, they're also total nonsense: TypeScript is 40th, behind Prolog, Visual Fox Pro, Dart, and Rust.
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.
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.)
I tried Rust, but when doing a data driven app, I found myself using unsafe way too much!
It’s like Rust forces people to acknowledge when they are taking things in to their own hands while C/++ let’s them live in blissful ignorance.
Also if you think memory safety is the hardest problem in programming, you probably haven’t written any non-trivial program anyway.
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.
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.
I'm porting this Python library to Rust: https://github.com/Blizzard/s2protocol.
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.
> 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.
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.
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.
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.
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'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.
[0] https://www.zdnet.com/article/linus-torvalds-rust-will-go-in...
A couple years ago I rewrote thousands of lines of Javascript into Rust (game engine & corresponding game server): https://www.reddit.com/r/rust/comments/k3jy5g/i_rewrote_10k_...
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
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.
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
I don't think that's an uncommon perspective.
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.
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.
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.
https://www.withsecure.com/en/solutions/innovative-security-...
Yeah you have to have high enough agency to use a subset of the language, but so what?
I dont plan on having 300 interchangeable human NPCs smashing keys on it, like Go was designed for.
That's 90% of the difference between pre-c++11 and post-c++11 best practices. And a couple other "dont do this" rules.