Especially with async which complicates lifetimes a lot.
Especially with async which complicates lifetimes a lot.
Rust is aimed and focused as a safe C or a sane C++ substitute, and is meant to intermingle with both. It is not an application "high" level language. You can use it as such, which is great. For anything you would use C or C++ you can use Rust instead. As a cryptographer I find that great.
Regardless of all the lack of latency or other control -- beyond fine tuning -- a garbage collected language makes critical memory choices we want to make instead. There are times we want to swap or use our own memory allocator, never mind having to add a garbage collector in the mix. (There is a good number of languages that scratch that itch, and you can likely link and use your C/Rust code with them.)
As far as async: also respectfully disagree. Async is sugar for here is a Future<..>. If you want to poll it locally you can. You can scope it also. If you want to use a cross thread work stealing algorithm you can. But you need like memory management to consciously make these design decisions. This is similarly why a lot of things are not built in in C.
This is part of why Rust is so successful -- it's the first real alternative for this space since C++ came along. For most application development, it's better to use a garbage collected language. But in the application space there is a much bigger choice of languages already available, Rust wouldn't have been a big deal over there.
The interpreted language even be slower than Python, so long as the escape hatch to Rust was simple and safe enough to implement the interfaces.
In Rust, lifetimes color your types, like async colors your functions.
It is a great condensed summary of what makes lifetimes a great difficulty of async Rust. It's a language that has the function coloring that is typically introduced by async (likewise in JavaScript), and on top of that the typesystem itself gets colored by lifetime annotations. You can have a well written and working program... then due to some new need or refactoring, wanting to add a 'static somewhere will cause it all to break down.
It's part of the language, nothing bad with that. But it is an extra layer of difficulty that needs to be mastered. To me it shows that Rust might only be the initial step towards future programming languages where this kind of issue doesn't lean so much on developer knowledge.
https://www.youtube.com/watch?v=p-tb1ZfkwgQ&t=340s
(in case it doesn't work: on the 5:40 mark)
I actually wouldn't mind a subset of Rust that targets the JVM.
Rust is arguably the first real contender to C++'s reign because it really brings to the table features you'd be a fool to pass on. D was nice, but it was not worth the switch. Eliminating whole classes of bugs instead is.
in the simplest case, you'd add Arc<..> everywhere @ used to be.
But such models would also take away everything that makes Rust... Rust.
IO complicated. The cycle of doing code then waiting for IO is wasteful (sequentially you waste millions of CPU cycles waiting for the network to answer back). To max out usage of your hardware resources you could just aggregate IO requests with your compiler. Switching back and forth between code that uses IO, as IO request come back (that's async)
Problem is: switching back and forth between code that uses IO is recklessly hard wrt coming up with a proof of the lifetimes of the variables.
And a language/runtime _needs_ to have the trifecta of compiler/GC-or-memory-management/memory-model coherent and within the runtime.
compiler/GC-or-memory-management: the GC-or-memory-management needs to know who writes, who owns, who reads
GC-or-memory-management/memory-model: you need to know when and how you can read your writes, what are the rules
memory-model/compiler: you'll be managing memory barriers so that you can cram together sequences of writes that are compatible, for maximal performance
This trifecta dependence is foundation to a language/runtime, and a change to one affects the others quite deeply. Changing the compiler (bringing async here) affects the other ends and you can't do that when GC-or-memory-management is all over the place (as a lib, or god forbid in user hands)
I'm afraid async is just something unaffordable for a language that wants to be that close to the metal. And even then, async is just a bandaid for a costly threading IO model.
----
Come over to the dark side of Erlang, Go, and Java. We have small threads now. You can just block like there is no tomorrow, and the runtime will have a cheap back and forth. You can just forget about the lifetimes, as the GC will sweep after you (and concurrently, outside of the critical allocation path). Forget it all my friend. Java is love, Java is life.
Plenty of GC enabled system programming languages have achieved similar feats, with bigger outcome than Rust has managed to on the desktop space, e.g. Xerox Workstations, across Smalltalk, Intelisp-D and Mesa/Cedar.
Redox is still not as feature rich as Mesa/Cedar was on the Dorado in 1981.
https://www.youtube.com/watch?v=z_dt7NG38V4
By the way Go, D, Eiffel, Nim, Common Lisp, Scheme, some JVM implementations are bootstrapped, meta-circular, with their own GC implemented on them.