That said, I still hate async with a passion, it makes the language more complex and not very elegant (i.e. function coloring). And now that I know how it works behind the scene (thanks to Jon Gjengset [1]), it feels so complicated and hacky, a mediocre very high level concept that someone managed to implement as a zero-cost abstraction. Impressive, but still a bad idea.
I'm sure the pro of having a BDFL instead of a committee is being able to follow a singular vision, instead of trying to appease members by adding the fad du jour which might stray a little too far from the original vision. Too many chefs in the kitchen and all.
Rust's async doesn't have the limitation author described – you can spawn and block both (Tokio has some limits there, but that's Tokyo's choice, not language limitation), making "color" largely irrelevant.
The second point was that both should have identical syntax, which Rust deliberately chose not to, because from Rusts perspective that would be too much implicit magic.
I stopped paying attention to the language around 2012-2013 or so when the direction clearly steered towards the latter.
You may want to check https://austral-lang.org/ out.
Being sync or async is essentially a property of the attention of the caller, not of the action itself. Is "eating a donut" a sync or async action? If I'm focusing all my attention on it, essentially putting all tasks aside (after) - then it's a synchronous action. If I'm reading a book/watching a video/walking/etc, while eating – it's an async action.
How does "func EatADonut() async {}" aka "eating a donut is an inherently async action" even make sense to people?
A nice talk about this: https://www.reddit.com/r/programming/comments/da141r/ron_pre...
I really wish people stopped using this concept, especially in the context of Rust because the `async fn`/“regular function” split is strictly equivalent to the `try_something()`/`something()` split (the first one being failible and returning a `Result` in case of failure). `Result`s and `Option`s are coloring the stack in exactly the same way a `Future` does (and `async` is pure syntactic sugar on top of future).
So, someone may like exceptions and green threads more than `Result` and `async` (and this is a completely valid PoV, even though I personnaly like the explicitness better), but thinking `async` is somehow special is just a conceptual mistake.
BTW, in the original blog post the “red” functions were actually functions with a callback parameter, which is actually very different.
Function coloring seems to come up a lot in these discussions, but I don't see a better way without providing a runtime. Could you propose an alternative approach to async, without sacrificing ability to write zero-overhead, high-performance bare metal systems?
I think designing and evolving any living programming language is just one of the hardest problems out there.
Incredible blog post indeed, was awesome to read it.
I feel like I need to point out that Donald “Structured programming with GO TO statements” Knuth included an example of coroutines in the first volume of The art of computer programming, the first edition, dated 1968. In assembly language for an accumulator machine. With a box of scraps!^W^W^W^W^WThat is to say, that C and most other languages have made coroutines awkward and thus virtually unused in the past three decades or so does not mean they are particularly novel or high-level.
Granted, Knuth used coroutines in a simulation and not for I/O, so he did not need that much of a scheduler, but still.
template<template<typename> class MyContainer>
int getFirstInt(const MyContainer<int>& myContainer) {
return myContainer[0];
}
(In Rust it's not possible to write something like MyContainer<int>; only the int is allowed to be generic).Trust me, you do not want to put this stuff into the compiler. It's not just that it's cheating for perf, but it's confusing to users (no source code to read how these work) and frustrating that they can't write their own. Ultimately what this really means is that these things are indeed written in some language--the compiler's IR. JavaScript actually has a ton of this kind of things and every JS engine has gone through multiple generations of "what language do we write Array.sort in!?". In V8, these intrinsics are written in a DSL because doing them in asm, a special dialect of C++, or one of two compiler IRs ended up being more trouble than its worth.
You want a clear separation between what is language and what is library.
Basically, I view Grayson as a leader who set the tone for Rust being a language that was willing to take ambitious swings on cutting edge features. But I don't think he would have been the person to eventually make the cuts and compromises necessary to hew the language into a cohesive, mainstream language. Rust ending up as a replacement C++ helped it not only determine which features to keep and which rules to follow, but also helped it create the right pitch for developers to use it.
This does lead to a larger question about BDFLs. Perhaps, like CEOs, the BDFL you want when you're starting a language is not the BDFL you want when you're maturing a language, or maintaining a language. Especially around feature selection, in the beginning it may pay off to add a lot of features based on user feedback, but later on it may be better to push back more. And from a psychological standpoint, I have wondered about the pressure of being a BDFL. Grayson has been open about stepping down partially due to reaching his limits, and I suspect other BDFLs have thought about it too. The job sounds exhausting and thankless. At a certain point, wouldn't you want to leave and start a new project? And wouldn't we want the person who had success once to give it another shot?
If there would be something just like Go, but with a bit more powerful typesystem like Rust has (Option<T> instead of `err != nil`, and so on), and a simplified ML-like language instead of an imperative one... that would be my dream.
A good example of this trade-off in Java is Lombok. A very handy library that legitimately avoids a ton of boilerplate, but it also absolutely tanks your build time. In a real system, a large one, your team is better off just getting good enough with their editor that they can generate the hateful boilerplate, and leave Lombok out. Because you'll be paying for Lombok all the time, and only need it a small fraction of the time. There are hundreds, thousands of these conveniences that are deeply tempting but should be avoided, in every build. The problem is that the programmers become attached to these little nicities and actively resist giving them up, even though they are so costly.
I know that the main point is about governance and how having a BDFL would have led to a completely different language but I really would have preferred the Graydon-BDFL-Rust to what we have today.
Very interesting article, worth a read.
It really is an incredibly language and ecosystem, in large part, because of its performance potential.
To be clear, the only real options in this space were arguably C, C++, and maybe in some circles D in my mind. C++ and C by far had the mind share.
Had Rust gone the way Graydon wanted I don't think Rust would be so interesting in the OS and Embedded space. This is a space that it turns out is really ripe for change.
Embedded application are growing more connected, and more complex all the time. Security is a serious concern perhaps followed by or proceeded by performance depending on who you ask. Rust checks so many boxes off in this space its really hard to argue that it isn't a better solution.
Would you rather write a little embedded http server on an IoT device in C, C++, or Rust? What about an embedded networking stack? What about a mesh network stack? I know the answer I'd have every time for this myself.
,,A lot of people in the Rust community think "zero cost abstraction" is a core promise of the language. I would never have pitched this and still, personally, don't think it's good''
If the language makes compromises in performance, it's not a real C++ competitor anymore.
Some things are not about what people ,,like'', but that we need a language that is safe and can compete with C/C++ in performance for systems level programming, as most security problems in the world come from C/C++ memory management.
If it's significantly slower than C++, Mozilla couldn't have picked it up to replace C++ code base, as there was a huge competition in performance between browsers.
"Complex grammar. I've become somewhat infamous about wanting to keep the language LL(1) but the fact is that today one can't parse Rust very easily, much less pretty-print (thus auto-format) it, and this is an actual (and fairly frequent) source of problems. It's easier to work with than C++, but that's fairly faint praise. I lost almost every argument about this, from the angle brackets for type parameters to the pattern-binding ambiguity to the semicolon and brace rules to ... ugh I don't even want to get into it. The grammar is not what I wanted. Sorry."
I also found this bit interesting: "It's easier to work with than C++, but that's fairly faint praise".
I see this kind of thing in my own personal projects all the time. I'm thinking "oh it would be really cool if I built X" when in reality most of the time users just want really simple stuff.
Being easier to work in than C++ might be faint praise, but it's probably the biggest draw of Rust for me. I don't want to touch C++ with a 10ft pole, but I love using Rust.
I find Rust basically unusable -- at the level of abstraction I want to write code, basic definitions break line limits.
Rust seems to be a repetition of C++'s mistake: a language which conspires you to pretend it's another. There are now nearly as many Rusts as C++s.
If I return to any domains where Rust would be relevant, I'd probably now opt for Zig or equivalent.
PHP show that a language only needs to get that one thing right.
Rust have found its niche. Graydons vision seem to be a more elegant language which would compromize on the points which actully make Rust succesful.
> (Swift at least traps in release by default -- I wish Rust had chosen to).
I enable it in release on serious projects:
[profile.release]
overflow-checks = trueAnyone writing these things today in C or C++ already understands object lifetimes and Rust just adds a static checker for them.
In such projects churning out lines of code is not the bottleneck, ease of development should not be prioritized over long term maintainability.
Why on earth would you try to rewrite python CRUD apps in Rust?
To give you a few examples:
- = instead of == for equality would have been the natural choice
- := for assignment is similar enough to what is used in math for definition, so that languages like Pascal use it
- <> for inequality is something SQL got right
Smaller things that bug me are the ubiquity of the double colon (::) and the weird mixture of snake case and camel case conventions.
And not to leave the wrong impression, I think Rust got many things very right. My personal highlights are:
- -> for the return value
- concise keywords like `fn`
- `where` for constraints
In general more Algol/Pascal and Haskell - less BCPL and C/C++.
I don't understand the subtleties here: Is tail calls an optimization the compiler can do irrespective of the language? Or is there something that prevents this and requires the compiler to use stack here? Is there any visible effect to the programmer from supporting tail call or not, other than performance and stack depth?
How does not supporting them compete with C++ performance?
I’ve always seen safety and lifetimes and borrowing as the main value prop, so I was surprised to see he was sort of aiming at an ML without GC, rather than a C++ that doesn’t blow up.
What is the rust experience wrt to inlining? Can every expression be inlined or only selected ones? How can you know what got inlined in some expression? Do you have to manually annotate every single function call you have to inline or is there a more general command?
Overall, a really interesting article. Though I like today’s Rust, I do think I would prefer the trade-offs made by the alt-Rust outlined here.
Perhaps it’s just my own personal preference, but I think there is a strong bias in users towards what they are already familiar with, and it’s hard to break away from those without a BDFl or similar position of authority who can impose their vision.
Makes me wonder if things like Linux, or C++, were historical anomalies, the stars aligned. How many good projects fail because of 'loosing arguments' that should have been won, or the community didn't form, etc... a million things..
I maintain the actor model is probably the most theoretically perfect concurrency and distributed computing model. The holy grail. We just don't have the right hardware for it and it's extremely limited by addressability issues with current technology.
So I don't really find this surprising, nor disagreeable. It's just not a model that Works Well at present.
Both Swift and Rust both veered away from their original champions. The champions helped by focusing the problem and providing a technical skeleton, but the need (the pain of C/C++/Objective-C) was both intense and complex, so the community was stronger than the BDFL model.
Interestingly, Swift has seen Rust forge ahead on a number of fronts, but is quietly adopting the best of Rust, and soon interoperating with C/C++ will be frictionless. The ties to Apple are being loosened, with a more portable stdlib and a Foundation library that subsets the legacy Apple Foundation instead of dragging Apple API's into other platforms. If/since Apple is to rewrite its systems in Swift, Swift will likely evolve into the best language for migrating off C/C++.
Compare Graydon, von Rossum, or Chris Lattner to Java's Mark Reinhold. Mark has been quietly at the helm of Java since 1997, navigating: the Oracle and open-source transitions, partners ranging from IBM to broad developer communities, continuous VM updates that kept Java relevant, and the quick pace of recent language/library upgrades: lambdas (method and field handles), vector processing and FFI, native...
I remember that one. The change was shortly after I started fooling with Rust and was major. Major as in it broke all the code that I'd written to that point.
"Async/await. I wanted a standard green-thread runtime with growable stacks -- essentially just "coroutines that escape, when you need them too"."
I remember that one, too; it was one of the things that drew me to the language---I was imagining something more like Pony (https://www.ponylang.io/).
"The Rust I Wanted probably had no future, or at least not one anywhere near as good as The Rust We Got."
Almost certainly true. But The Rust We Got is A Better C++, which was never appealing to me because I never liked C++ anyway.
My interest back then was in a higher level language with type inference and a modern ML-like type system but that could be used for systems programming, especially in database, virtual machine, and even operating system dev.
These days I work pretty much full-time in Rust, and I think Rust as it is today delivers on some of that promise, but not all. I feel like the language's borrowing and ownership checking are pretty brilliant but really begin to become a pain when dealing with nested and interrelated trees of objects and iterators (like if building a compiler or query evaluator, etc.), and resorting to Arc/Rc/RefCell, etc. feels awkward.
I'm not sure if the language Graydon talks about here would have been better for that or not.
But I'm also happy we have Rust, because it's an improvement over what else is out there, and I hope the community gets through its growing pains.
Realizing this, I thoroughly feel the need for a “Rust, the good parts” doctrine.
A good portion of use-cases could be successfully implemented with a small subset of language. The small subset doesn’t need to be any more complicated than Go. And in doing so, we’d be reducing the entry barrier for masses and encouraging wider adoption.
Edited: For clarity
>"Hello, you've been (semi-randomly) selected to take a CAPTCHA to validate your requests. Please complete it below and hit the button!"
...pops up and the button doesn't actually work. Truly one of the worst blog hosts out there if you actually want everyone to be able to read what you write.
One of the things I like about Delphi is that it has some powerful types as compiler intrinsics. Sets, strings (the compiler-generated code does call into RTL methods for things like finding substrings, but the string type itself), and so forth are all compiler-generated.
I would like to see more, in fact: I think a map type would be a great inbuilt addition. (What I'd really like is compiler stubs so you could link in your own implementation. Whatever is linked in, it's then heavily optimised by the linker to be inlined etc as appropriate.)
Graydon wanted something else even before Rust 1.0 was released. He wanted an OCaml-like language with modern Go/Erlang-inspired higher level concurrency abstractions.
- Explicit lifetimes are what make rust what it is. It would have surely failed had they not been introduced.
- I disagree about having a first-class module system instead of traits. Coherence and implicit instance resolution are a core value of Rust. `Send / Sync` are key examples of that.
- Green threads probably wouldn't been viable for rust, given the kind of programs it's targeting.
- Pretty much everything else however, I agree with.
I see the ongoing attempts to add linear types for low level coding, alongside automatic resource management more future proof.
Isn't that a matter of self selection? A language which developed around those priorities would have a community sharing those priorities now.
The point is if that community would be as large as the current one, larger, smaller.