8 ms·
I learned Rust by writing a (fuse-based) filesystem [0]...about 30k lines iirc. Rust was challenging, but not crazy difficult (it helps that I have a C++ backgr
by daenz 4y ago
I learned Rust by writing a (fuse-based) filesystem [0]...about 30k lines iirc. Rust was challenging, but not crazy difficult (it helps that I have a C++ background). I loved it. Development was slower than a dynamic language, but faster than C++, and most importantly, I felt safe. It's a really solid language.
However, when I took a look at async Rust, it really did appear to be a mess. I have substantial experience with Python async/await (which is also a mess), so I'm not unfamiliar with the async concepts. Honestly, I think it's the idea of an event loop in a compiled + non-memory-managed language. You really have to think hard about where objects are living and for how long, and combine that with the illusory world of how async/await appears to work (versus how it actually works), it gets hard to conceptualize. Maybe I just didn't spend enough time with it to feel comfortable with it, but that's my hot take.
Imo, Go does performant concurrency right. Rust would be smart to adopt what Go offers.
0. https://github.com/amoffat/supertag https://github.com/amoffat/supertag
- whatshisface 4y agoGo has a runtime and a GC, and importantly can implement an event loop outside of anything reached by tracing execution from your code's entry point. Rust's philosophy lead them to make the runtime something you bring in with a library import and start manually. Bundling Tokio with every binary would not be their way, although something like it may one day wind up in the standard library.
- erikpukinskis 4y agoDoes Tokio have a concurrency model similar to Go's?
- oandrew 4y agoNo, since rust async is stackless. There is a stackful coroutine implementation for Rust: https://github.com/Xudong-Huang/may https://github.com/Xudong-Huang/may
- steveklabnik 4y agoRust cannot copy what Go does without compromising on various language design goals. What Go does is good, but what’s good for Go isn’t always what’s good for Rust. That goes the other way too :) Rust did try to have something closer to Go before 1.0, but it led to so many issues it was removed. Those issues aren’t a problem for Go.
- ssokolow 4y agoHere are a couple of examples of the changes that were made in the two or three years before Rust 1.0 if anyone wants to read more: https://github.com/rust-lang/rfcs/blob/master/text/0230-remove-runtime.md https://github.com/rust-lang/rfcs/blob/master/text/0230-remo... https://pcwalton.github.io/2013/06/02/removing-garbage-collection-from-the-rust-language.html https://pcwalton.github.io/2013/06/02/removing-garbage-colle...
- Karrot_Kream 4y agoMy path to Rust async sanity was using lots of Rc. Though at times when I'm using lots of Rc, I question why I'm not just using a GC language. I really like Go as a language and you can approximate a lot of its development style by just using channels similarly in your own code. I frequently employ crossbeam channels to that effect.
- felipellrocha 4y agoI was about to say. You could avoid a ton of ‘Rc’s by just using channels instead.
- xxpor 4y agoTo a certain extent, it's just enforcing what you have to do for correctness. If you have concurrency (even without parallelism), and objects are shared across contexts, you have to keep track of when objects are in use or not. The other option is enforcing exclusive access (which is just a way of maintaining a refcount of 1) At least Rust makes it easier with an Rc/Arc type! Can't tell you how many refcounting bugs I've dealt with in C since you have to remember to increment and decrement the count manually.
- ncmncm 4y ago"Using lots of Rc" is what we call Java Disease elsewhere. Usually it indicates trying to sidestep architecture problems instead of solving them. Often you Don't Care, and anything that works well enough lets you move on to the next thing. As long as you and people around you agree on the goal and tradeoffs, it is OK.
- msbarnett 4y ago> Though at times when I'm using lots of Rc, I question why I'm not just using a GC language. This seems like the wrong way to look at it? Focusing on the spots in your code where you’re using RC and concluding you ought to be using GC misses the fact that all of the spots in your app where you’re getting along fine without GC would suddenly be forced to bear the costs of GC. And RC isn’t a poor man’s GC; Apple concluded that Automated Reference Counting was a significant memory and CPU win over running a GC. Unless you’re littering RCs literally everywhere, you’re probably not doing anything much worse than ARC does in Swift (in fact you’re probably doing better if you don’t do it anywhere the compiler didn’t force it, because Swift has less lifetime info and is forced to be more defensive) – you’re more than likely coming out ahead versus what your runtime costs of a GC would be. (But if you’re not concerned about runtime memory or CPU cycle costs at all, then sure, Rust is probably at the wrong level of abstraction for your problem)
- jchw 4y agoExactly. And worse, async in Rust is viral, because any code outside of async doing I/O is not good to use inside of async. And it gets worse: making everything async is not a good option. Effectively, it seems there is no viable answer: you need two of everything. Go does do concurrency well, but unfortunately it’s only able to do that because of opinionated decisions that a language with Rust’s design goals cannot really make. They also have tradeoffs that I don’t think Rust developers would accept, like making calls into C functions slower. So I feel as though Rust async is at an impasse. It can be made much better, but it feels like the improvements will come at increasing complexity in the language, compiler and ecosystem. Meanwhile, the ideal end state seems like it will still have a lot of annoyances, such as the need to tirelessly duplicate anything that needs I/O for sync and async. I almost kind of wish Rust would just drop async, as the rest of Rust is much better, having only minor issues that are very much fixable IMO. Instead the ecosystem is accelerating into it, and now it’s hard to avoid for some use cases. I hope it all works out, because Rust is good and I’ve been advocating for its adoption at work and continually trying to adopt it elsewhere. But the issues with async are very dire, imo. Async rust is cool, but it isn’t what I would consider to be a similar level of robust, thoughtful design.
- daenz 4y agoIf Rust does decide to continue with async, they should really look into investing in improved docs and education around it. When I was looking at it a few years ago (maybe it has improved since then), I really did feel stupid for not getting the ideas/conventions/reasoning. If it was that off-putting to me, as someone with some experience, I can imagine it is very unapproachable to complete newcomers. It just felt like I was looking at something that shipped way too early.
- ollien 4y agoThe async chapter in Rust for Rustaceans was a great introduction. It leaves something to be desired because you walk away with it without knowing how to run any async code (it doesn't mention any runtimes, for instance), but it explains the concepts really well.
- 4y ago
- ssokolow 4y agoThe problem is, Go uses a concurrency model (stackful coroutines/fibers) that was all the rage in the 90s (to the point where the Windows APIs have vestigial support for it) and then got abandoned by everyone except them for reasons that are still applicable. Here's a paper on the history and flaws that was written to argue against adding them to C++: "Fibers under the magnifying glass" by Gor Nishanov https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1364r0.pdf https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2018/p13... TL;DR: They don't play nicely with FFI and that's a big part of why Go is its own little semi-closed ecosystem... the polar opposite of Rust, where things like PyO3, Helix, Neon, cbindgen, etc. are a huge competitive advantage.
- samwillis 4y ago> Python async/await (which is also a mess) This! There seems to be a move to (somewhat) "async everything" in Python. But it just results in bad developer UX, it's verbose and unneeded 95% of the time. I wish Gevent had been adopted as the starting point for an official way to do "none threaded" parallelization.
- kosherhurricane 4y agoLack of 'async' in Go has been a great design choice (aka, an absolute blessing). People complain a lot about Go's missing features, and they did end up adding generics, but their conservative approach I think has been a net benefit for Go. Rejecting language feature is something that's hard to do, and it takes a lot of experience, and I dare say wisdom to stick to it.
- kibwen 4y agoIt's the other way around, Rust tried Go's approach and rejected it. The original incarnation of Rust was heavily inspired by Limbo, which was the language Pike developed prior to Go. As of 2011, you could describe Rust as a slightly lower-level Go, it even had (a poor stub implementation of) a GC. Rust's Go-style green thread runtime was removed in 2014 because it complicated the C interop story too much (which is a tradeoff Go is happy to make). It's not a matter of minimalism or simplicity, it's a matter of different priorities. Rust's stackless async support is suitable for systems like microcontrollers, and the design reflects that.
- mwcampbell 4y ago> stackless async support is suitable for systems like microcontrollers, and the design reflects that. Tangent: How feasible would it be to have a language where the required stack space for all function invocations is statically known at compile time, just as the sizes of Rust futures are statically known? I guess this would be even harder, because with async functions, the compiler only has to store the state of the function in a statically sized structure when it reaches an await point. But I'm guessing that running out of stack is a common problem when developing for microcontrollers.
- kibwen 4y agoYou can predetermine function stack sizes as long as you don't do anything resembling C's "alloca" (Rust doesn't support alloca directly, but it supports dynamically-sized types in some contexts which would stymie this). But from there you need to use that information to figure out the maximum stack usage of the program, which means exhaustively determining all possible execution paths, which can be a hard problem (you also need to forbid recursion, even mutual recursion, which languages generally don't try to do (something something y-combinator)).
- linkdd 4y agoI'd like to have some clarification on the following claims: - Rust async is a mess - Python async is a mess What does "mess" means here? I have been writing Rust code for a few months, and Python code for more than a decade. I have to admit that I do not know the internals of the Python's async model, but as a "user", I think that trio (and asyncio with 3.10 / 3.11 is getting better) is really great. The ability to call `trio.run` / `asyncio.run` anywhere to start an async loop does not "contaminate" the async/await keywords up to the entrypoint. Which is great. In Python, calling an async function returns a coroutine object (that can be awaited). In Javascript it returns a Promise. In Rust it returns a Future. From the "user" point of view, it's all the same. And I don't know many developers who are interested in how it works under the hood, because (like in math/physics/science in general) it's very useful to sit on the shoulders of others. If I understand correctly, Rust gives you the tools, and the "shoulders" belong to the library developers (like tokio). Which is fine to me. So, I really do not understand what "mess" means, what it refers to, and what I (as a "user") can do about it. Also, if the internals change, how will this impact my code?
- bvrmn 4y agotrio and stdlib has different primitives for sleeping/locking/garhering/etc. And you can't change loop implementation by simple ioloop_of_this_year.run(async_main) in the main().
- linkdd 4y agoThis is nitpicking, I never said that. There is still no explanation for what "mess" means, but I don't think it means having multiple competitors providing an implementation. The fact that there are multiple async Python libraries means that the internals are generic enough to allow that. From what I understand of Rust, there could be another "tokio" as well. And they would not necessarily be compatible either. This is like saying "You can't use a Django model with SQLAlchemy".