10 ms·
A four year plan for async Rust
- inamberclad 3y agoAsync is a huge wart and I try and avoid it wherever possible. It's simply not useful in a lot of cases that I encounter. However, if a library uses async, you have little choice but to make your whole project async. This adds to its horrible reputation.
- the__alchemist 3y agoI'm with you. I feel like an outcast in the Rust OSS embedded circles when I bring this up. They are heavily into async/embassy.
- dgacmu 3y agoI'm curious why you don't like it for embedded - embassy has been an absolute delight for me thus far, and my primary complaint about it is just that it doesn't have the breadth of hardware support (yet?). It's been the thing that has redeemed Rust async for me, as otherwise, I tend to find the tension the author notes frustrating as well.
- steveklabnik 3y agoI am pro async Rust, but we don't use async in our embedded projects at Oxide. This is because of specific design constraints and goals: https://hubris.oxide.computer/reference/#_why_synchronous https://hubris.oxide.computer/reference/#_why_synchronous That being said, I am also a fan of embassy when you have different design constraints and goals, and consider the fact that is is able to exist and be successful is a massive testament to the design of async Rust. (We also use async Rust heavily further up the stack, and have some issues with it, but they tend to be disjoint from the way that this is talked about online.)
- dgacmu 3y agoOddly, I agree with you - but I think I may be approaching it differently. I use async as a mechanism to be able to have clearly-defined "tasks" in an embedded context, where tasks have straight-line code that handles something. I have most of the interaction between tasks be synchronous; in the case of embassy, the thing it brings is that it manages that otherwise-spaghetti-feeling mix of state machines in an easy-to-read kind of way. Example from a current side project, since it's not work-encumbered: A wifi-enabled clock light for my 5yo. It has a task that sits there and every hour pings an SNTP server, updating a (mutex-protected) global with the time state. It has another task that listens for a telnet session for various control signals - which also updates a mutex-protected global config state. And it has a task that spins doing LED effects. With embassy/async, I can write each of those as a separate task, without paying much attention to what gets invoked by interrupt handlers. (This particular one is an rp2040, but I use it on stm-based systems as well). I feel like this is kind of analogous to the discussion of threads-vs-events as a mechanism for structuring code vs. threads as a mechanism for achieving parallelism. :-) Edited to add: Or, perhaps an alternative view of what I'm doing is that I'm using the embassy runtime as a really lightweight alternative to an RTOS, since I mostly haven't met an RTOS I don't want to throw across the room. An argument against what I'm saying here is, "well, use Hubris as your embedded OS and then you can have tasks and they can be synchronous" - which seems entirely fair.
- steveklabnik 3y ago> I use async as a mechanism to be able to have clearly-defined "tasks" in an embedded context This is also a good design! The primary designer of Hubris also has a project that works like this: https://github.com/cbiffle/lilos https://github.com/cbiffle/lilos
- the__alchemist 3y agoWhat I like about Embassy is the Metapac, and less reliance on generics and typestates than predecessors. My complaint is I don't find the async ergonomics intuitive; it feels like a layer of misdirection. And, the viral character.
- littlestymaar 3y ago> However, if a library uses async, you have little choice but to make your whole project async. This adds to its horrible reputation. No. This is how yo do: you look what executor your dependency is using (most likely tokio) you add it to your cargo.toml (at zero cost, since it's already there in your dep) and then you wrap the async library calls in `block_on` and call it a day. You don't need to change a single other line in your project.
- wongarsu 3y agoAnother viable strategy is to start a single-threaded tokio executor and treat it like another thread that you communicate with over (flume) channels.
- littlestymaar 3y agoYes this works too. People complaining about async being contaminant are often so much prejudiced against it that that haven't even tried to understand the basics.
- dymk 3y agoI have plenty of gripes about async in rust, but one of the things it's surprisingly good at is isolation of async runtimes. There's no reason you can't have multiple tokio runtimes, or transient runtimes, in your application. Now if only the other warts were fixed, like the particularly poor compiler errors when there's an issue within an async function...
- tuetuopay 3y agoYup. I was blown away when I spun up five different tokio runtimes in the same app all communicating through channels. I expected at least the tokio-aware channels to be problematic but not in the slightest. It's now humming in prod doing its business. Once you get the basics, it's quite easy to lego stuff out and get the best of both worlds.
- 3y ago
- echelon 3y agoWhat on earth are you talking about? Async is amazing. Super fast servers in Actix with multiple DB connectors performing simultaneous queries is super easy to set up. So many people complain about Rust being hard. What on earth are you people on about? This isn't at all a hard language. I'm so sick of this "async is hard" / "Rust is hard" meme. No, it isn't. Maybe it's inconvenient if you're used to slapping packages together and calling it a day. But I don't think the majority of us do work like that. And it's not hard.
- seanw444 3y agoThe point can be made equally well without the elitist condescension.
- echelon 3y agoIt's not elitist. I'm sick of people perpetuating this about the Rust ecosystem. It's not a month that goes by that there isn't some article badmouthing the language and its features. Stop it. We want our language to gain support in enterprise so we can get paid to write it as our day job. These articles and negative comments are not helping.
- dymk 3y agoThis article is by a Rust contributor. I'm sure they want to see Rust be more widely adopted as well. Async is pretty great, but it's not perfect, and in some situations its shortcomings cannot be worked around easily.
- withoutboats3 3y agoI think the comment this person was responding to is completely off topic for my blog post, and extremely shallow. I share the person you're responding to's frustration with how async Rust is talked about on Hacker News.
- 3y ago
- starcraft2wol 3y agoThe entire unix operating system is designed so that you can write sequential code. It abstracts concurrency away. It’s incredible.
- deleted 3y ago[deleted]
- gwbas1c 3y agoThe point of async is to move concurrency from the OS into the process. The specific issue is the context switch. The compiler, with async is able to be much more performant than the context switch that the OS provides. Depending on the kind of application you're writing, this is either splitting hairs or very, very important. Applications that handle many (hundreds, 1000s,) of concurrent IO will have a noticeable performance improvement using async versus the OS's context switching. But, there's a more important thing to consider: "async" in a programming language communicates that a method can block. It allows the caller to start the call and do something while it's waiting for the result, without needing to get into the weeds of threading. Purely relying on the OS for context switching means that it's hard to know what methods block.
- starcraft2wol 3y agoI agree with most of this. > without needing to get into the weeds of threading A thread is the exact concept needed to describe that and preserve “if else then” sequential code. > 1000s of threads Linux handles thousands of threads just fine. If you’re using a scripting language like python, context switching is the least of your performance concerns.
- gwbas1c 3y ago> A thread is the exact concept needed to describe that and preserve “if else then” sequential code. I suggest looking at C# and Javascript that implement async very, very well. The difference is that with conventional threading semantics, when you join a thread, it doesn't return a result. You still need to write some kind of "thing" to get your result from the subthread to whatever's waiting on it. (C# also provides a less-well-known BeginInvoke mechanism which is somewhat cleaner than join.) In contrast, the promise (Javascript) or task (C#) has a result. Instead of joining a thread, the await keyword gets the result, just like calling a method. > Linux handles thousands of threads just fine. Yes... And no... It doesn't matter what OS you're on, each thread needs its own allocated stack space and has the overhead of context switching. "async" optimizes that by putting data that would normally go into many different stack spaces into the heap and jumping around among concurrent operations without the context switch. Again, depending on what you're doing, that's either splitting hairs, or really making a tangible improvement. But you can't argue that more allocated stacks, and more context switches, is faster than doing it in process. At that point you're arguing with fact.
- withoutboats3 3y agoWhat Rust libraries use async that you think should not? I can think of one, but otherwise every library I've encountered uses async because its intended for the kind of networking service that benefits from using non-blocking IO.
- couchand 3y agoJust because it's a network request doesn't mean it benefits from using async. EDIT: to add a specific example, postgres. I understand from sfackler's perspective why it makes sense to maintain just an async library and a sync wrapper around it. But from the user's perspective there'a absolutely no reason that all of tokio should be required to talk to their db.
- withoutboats3 3y agoAs a user who has maintained network services that communicate with Postgres, I'm very glad that sfackler's postgres library doesn't perform blocking IO calls, as that would make it unfit for purpose for me.
- couchand 3y agoMaybe you misunderstand me. I'm not saying there shouldn't be a non-blocking version. I'm saying async is not a silver bullet. Some programs/teams/environments have different constraints than you. Would you also advocate for the kernel to deprecate blocking i/o?
- withoutboats3 3y agoObviously, it'd be better for someone who wants to talk to postgres and doesn't need non-blocking IO if a version based on blocking IO existed. But what is your grand point? It's not some conspiracy that libraries for network services use async/await: they most often do it because their maintainers need to use async/await in the work that pays for them to provide this open source library free of charge to you. And all you need to do to not deal with async is wrap it in one of the many different block_on implementations. Yes, that's less ideal than not having to pull in these dependencies; if avoiding those dependencies is mission critical for you, maybe yours could be the enterprise that pays for the blocking IO postgres library.
- dymk 3y ago> However, if a library uses async, you have little choice but to make your whole project async. This isn't true. I'm writing an application right now that is mostly sync, but has a small amount of async code in it. It's easy to spin up a tokio runtime which can run inline on the current thread. Then use it to evaluate a Future. tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { println!("I'm async!"); });
- killercup 3y agoBecause this code block looks quite complex, I want to add that it can also be just smol::block_on(async { println!("I'm async!"); }); (I thought tokio had a helper like this too but could only find `tokio::runtime::Runtime::new().unwrap().block_on(async { println!("I'm async!"); });`.)
- cedws 3y agoNeeding a helper library for something as simple as async so you don't go mental is really not good enough. I see the same thing with error handling - every Rust project I see imports a helper because it's too clunky otherwise.
- mplanchard 3y agoIf you don't want to pull in a helper library to run async code in a sync context, then why pull in an async library at all? Rust is not a batteries-included language like Python. There are lots of libraries that are very commonly used in most projects (serde, thiserror, and itertools are in almost all of mine), but this is a conscious choice. They say in Python that the stdlib is where projects go to die. I'd rather have the flexiblity of choosing my dependencies, even for stuff I have to use in every project.
- Elinvynia 3y agoThe problem is that a large number of popular libraries has converted to async, 95+% of them to Tokio. So you are stuck with smaller, less battle tested products if you'd rather not pull in 100+ crates of dependencies that are doing nothing but inflating the build times and file sizes (for your particular usecase). Example: reqwest vs ureq
- lucasyvas 3y agoThis is consistent with the author's assessment - the incomplete implementation of async makes it very challenging to advocate for, despite the fact that it was the only logical choice given Rust's design goals. I will say that the notion that you must make your whole project async is largely true (except that you can block on futures with all runtimes), but this is more symptomatic of the fact that Rust has hitched its horse to two wagons that don't have clean overlap. It is effectively two languages trying to converge in the middle. It's equally desired at "web server and above" and "operating system and below", which probably makes it one of the hardest languages on the planet to design (lets not forget it deliberately has no runtime, which makes things even more difficult). Whether or not this is a good idea remains to be seen over time, but they are in largely uncharted territory so I'm prepared to give them some slack on it while they figure it out. That said, holy has it taken such a long time to round out the async story to make it feel better. I don't blame users not wanting to wait it out because it has felt like an eternity.
- Verdex 3y ago> It's equally desired at "web server and above" and "operating system and below", which probably makes it one of the hardest languages on the planet to design. My theory as to why C++ has become as a problematic language as it has is because it is able to do it all. And "all" doesn't fit nicely into a single package or paradigm. I don't like async (in any language), so your post immediately piqued my interest. I'm not that interested in 'webserver and above' so of course that would be where people find async useful. It is possible that there is no way to unify these two domains into a single language (at least without becoming c++). Although, then again, it might be that if you think about it long enough then the unification mechanism becomes apparent.
- zozbot234 3y agoC++ is problematic in that it's a memory-unsafe language with a lot of cruft dating back to the 1980s. Its generality has made it more not less successful, since that makes it a lot easier to reuse library and support code across domains.
- 3y ago
- CyberDildonics 3y agoYou are absolutely right. Integrating 'async' into a language is not a good approach to concurrency. If you want to run a single function on a different thread it can work. If you want something to run after that, that can work out. Once you go beyond that, you are building a graph with very crude tools. Then you have lots of problems, including how you handle packaging dependencies when something you want to run depends on data coming from multiple other async sources. Treating this as a language issue and not a library and tools issue is a huge mistake. Another reason is exactly what you outlined - libraries getting infected with a languages half baked concurrency solution instead of doing what they are supposed to while the user can fit them in where they want. What actually works is graphs that handle dependencies and data structures for synchronization, but ultimately those need to be done well too.
- orcy 3y agoI am in doubts about async too. For better understanding I have implemented my own executor and io library (even with some tricky async destruction) and I am not quite happy with it. The problem is that in rust there are no other good methods to write safe async code, are there? My impression that borrow checker forbids lots of patterns I know from other language.
- no_wizard 3y agoI will forever argue that things like goroutines and the Java Loom Project are proving that it is long term more successful than making an explicit async keyword / operation that has its own semantics when invoked, because it won't dictate your code and cause the color function problem to exist. I'd love to see this in Rust, and I know that it initially had something like this, perhaps its not a bad idea for someone to take a second look at this now that more time has passed. EDIT: to be clear, that is not me saying we need goroutines or we need a Java Loom equivalent in Rust, simply, the DX of these two examples are far superior to the DX of async Rust today, in my opinion
- withoutboats3 3y agoThis was the subject of my previous post: https://without.boats/blog/why-async-rust/ https://without.boats/blog/why-async-rust/
- insanitybit 3y agoHow can you argue that? Loom is extremely early, it's not proving anything. And Java/Goroutines don't have a keyword because they do things implicitly, they have heavy preemptive runtimes.
- no_wizard 3y agoThats red herring IMO. Sure, they're GC'd languages, but that does not preclude the fact that both of these approaches from a DX perspective are easier to work with, and that matters in language design. I didn't say we need goroutines or we need Loom style asynchronous primitives I am however pointing out, that they did a really good job of making async approachable and feel like you're writing the same code as you would in a traditional synchronous model. Thats the real win. Its not an easy problem to solve, to be absolutely clear, but its a worthy goal, and if that means it adds marginal overhead initially I think its worth the tradeoff (esp. if you can do something similar to how you can use Rust in a `no-std` build, you could do one without a `async runtime` build, spitballing off the top of my head) Another problem is that generally speaking, async is de facto used a 3rd party lib, and it really should be a first party primitive that everyone feels comfortable using. To me, this is what matters
- deleted 3y ago[deleted]
- jokethrowaway 3y agoWell, you can block on it if you want That said, I disagree on the usefulness of async: in my experience it does the job and it's my default setup. There's a complex project where I ended up just using threads because I needed to squeeze performance out but overall async is great for tasks, sequence of async operations. Still, it had some rough edges: (it's been a while but) using Async Closures wasn't pleasant and I re architected my app to not use them as a result. There's an argument to be made for this change making my application easier to reason about - but overall it's poor language flexibility.
- untitaker_ 3y agoThis is a popular sentiment that I share to some degree, but I really wish we could just move on from this discussion. It happens in every post that is even just vaguely about Rust or async. It's like reading complaints about the GIL on a post that is loosely connected to Python! At some point it just gets boring. "I will forever argue that [...]" -- Why are you forever arguing? "I feel like I am alone with [...]" -- No, I read it every day on here.
- no_wizard 3y agoto be honest, HN is one of the few venues where I think someone who is core to a project might actually see what is said about a language. I've tried getting involved in mailing lists and stuff, but they aren't often as welcoming to this discussion as one might assume. My hope is that by raising these things on HN, someone will take notice in a different way and at least start considering / revisiting alternatives
- untitaker_ 3y agoAll of this would be correct in an alternative reality where everybody in the Rust community, including every person ever involved in the language's evolvement isn't aware of these complaints.
- Kranar 3y agoYour comment is self defeating. The reason people are aware of these complaints is because they keep being brought up, over and over. This is a serious wart on language design and while I can agree that it's likely too late to fix it for Rust, there is a kind of race among new languages to be a successor to C++ in many of the domains C++ is used in, and while I think Rust does hold a lead in that race, the race is not over. A language that can provide an ergonomic solution to concurrency would absolutely provide a huge boost to any such language, and so to people in that space, listen to these complaints. Async/await is not a good solution to this problem. You almost always hear people complaining about async/await in every language it's a part of but you rarely hear people complain about how Go manages concurrency.
- nextaccountic 3y agodoesn't block_on work for you?
- empath-nirvana 3y agoI genuinely don't understand the problems people have with async code... at least for anything involving http/api requests it's largely just a matter of decorating stuff with 'async/await'. It makes a few things difficult (iterators with futures, ugh), but mostly it's easy.
- galangalalgol 3y agoBecause rust is a systems language, it is common for people to use it for things that don't involve http or even networking in general. Alternatively, people often use it for wasm. Before async and specifically tokio, a much larger percentage of crates were usable by those people. This feels like something nice has been taken away or that our use case has been marginalized. The answer is to spend the time to create tokio and/or async free alternatives to these crates, but that was not expected to be necessary, so it also is added work. All of that is irritating, and the constant stream of "just accept it and get with the program. Stop complaining!" Is really quite infuriating. The fair answer is that rust should be what the majority of its users want/need it to be. But people who just need a back end language have a dozen or more good ones to choose from. People who need a systems language have far fewer choices, especially if you want memory safety, so having the aims of the language diverted from that end is irritating to say the least. I do suspect a fork to occur someday for those reasons, probably centered around the use in linux android or windows.
- deleted 3y ago[deleted]
- andrewstuart 3y agoI thought async and await were kind of logical builds on top of generators but this is saying rust has no generators. That’s how it works in javascript and as a commenter mentions in Python too, right?
- bobbylarrybobby 3y agoThey are logical builds on top of coroutines, as are generators. There is a generator initiative in the works, but it does seem odd that async exists and generators don’t given that the “pausable state machine” nature of them is so similar.
- potatochup 3y agoThat's true for implementations like python async, but rust operates a bit differently (mostly to avoid allocations across await points). The author of this article has some other very good posts about it.
- __s 3y agoThey're intertwined, but generators have yet to stabilize https://doc.rust-lang.org/beta/unstable-book/language-features/generators.html https://doc.rust-lang.org/beta/unstable-book/language-featur...
- Sprocklem 3y agoThey are in some languages, but Rust instead implemented them with a pollable Future trait. The async keyword, then, causes the block to desugar into a new type that implements Future. In order to emulate a coroutine, the generated poll function includes a state machine that tracks the await point where the function was suspended.
- kmeisthax 3y agoIt's also how it works in Rust. Stable Rust isn't allowed to use generators, but the code that desugars `async fn` is, because it's part of the compiler.
- vlovich123 3y agoAdding Move and deprecating pin/unpin would be a huge ergonomic improvement I think. I also think the Rust project should consider “out-of-band” editions if they can deliver big features like that sooner rather than waiting until 2027.
- alilleybrinker 3y agoNote that in general, the Rust project has chosen a time-based, not feature-based, release model. New Rust versions come out every 6 weeks, and new Rust editions have a consistent cadence of every 3 years. There are advantages and disadvantages to debate between time-based and feature-based release models, but Rust pretty clearly has chosen their preference.
- zozbot234 3y agoThen they'll just add a new #[from future import Move] syntax.
- pornel 3y agoThe important aspect of the 6-week releases is that they’re frequent, so nobody feels a need to rush a feature to cram into the next big release. Editions happening every 3 years unfortunately undo this, and there is a pressure to land changes before the upcoming edition.
- mplanchard 3y agoI agree that Pin is super confusing, and a Move trait would be much better. I'm willing to wait though :)
- lucasyvas 3y agoI think std needs a default runtime, and we might as well make it tokio, but maybe make the single-threaded executor the default instead and tweak the API where appropriate to align with this change. Swapping the executors out should absolutely be a feature, and the traits should be portable, but a way to start fixing the situation beyond the great suggestions in this proposal is to acknowledge that std and no-std users are different, and std users are often developing applications and prefer sane defaults.
- steveklabnik 3y ago> I think std needs a default runtime, and we might as well make it tokio Regardless of the quality of this idea, it isn't going to happen: neither the Rust Project nor Tokio (in my understanding, to be clear I am not involved in either) want this to happen. > Swapping the executors out should absolutely be a feature, and the traits should be portable I am not fully aware of all of the details here but there are significant problems when it comes to actually getting this done; I don't think anyone is ideologically opposed, but there's a lot of practical considerations that make this difficult, in my understanding.
- alilleybrinker 3y agoTo pick out one example of what makes it challenging: not all executors place the same type system constraints on tasks they execute. Tokio, for example, is a work-stealing executor which requires tasks to implement the `Send` trait so they can be sent between threads, while other executors may never move tasks between threads and therefore don't require the `Send` bound.
- lucasyvas 3y agoYes, this is a major difference. But at this point, let's be honest - why has most stuff converged on Tokio? IMO it's not for reasons wholly of merit - it additionally won a popularity contest as one of the first movers. Why the popularity contest? Because people want an async executor, but they just want it to work. These people largely don't care about the benefits between different executors. I think they'd like the choice to use a different one, but they'd rather just having something available with async traits that trend toward opinionated. The executor issue is a huge problem, because the ideology of "zero opinion" on executor coupled with "ease of adoption" are completely at odds. I don't think the current trajectory will ever resolve nicely.
- qaq 3y agoI kindah lost track what is the current status of async in traits ? Still relies on the macro?
- deleted 3y ago[deleted]
- withoutboats3 3y agoThe feature is shipping before the end of the year.
- sargun 3y agoI was a huge opponent of Rust, but finally decided to give it a try in anger once again. I began writing a large application, and noticed that many of my libraries only offered async versions, and the promise was quite appealing -- not having to worry about threads or concurrency as long as I followed certain rules. What I ended up with was an incredibly slow application because of the limitations of Rust async, and the runtime. All of my I/O got pushed through one thread (with tokio), and that, plus scheduling overhead became my bottleneck. Debugging this was a nightmare in writing my own tracing tools. 20/20 hindsight, I would not write my code to be async, and would just prefer threads. I'm really not sure how / why async took off the way it did.
- insanitybit 3y agoIO shouldn't be going to one thread, as far as I know. Blocking IO would go to a threadpool. But you can just `block_on` your futures if you want and not think about it at all.
- cmollis 3y ago+1
- jackhalford 3y agoasync took off when multicore processors came out and C had no first class way of running in parallel, so we bolted on threading libraries that are second class. Take a look at zig’s approach to concurrency, it’s so first class you can write your own event loop without an OS, i.e you could use the language’s async to write an OS.
- Sharlin 3y agoAsync doesn’t really have much to do with multicore. Indeed the most used async environment on the planet (JavaScript) is (used to be) strictly single-threaded. Async is all about keeping the CPU busy even though the stuff it does involves latencies thousands or millions of times longer than CPU timescales – and about abstractions that allow you to pretend you’re writing normal synchronous code when the reality is anything but. This mostly just involves chopping up the code and turning it into a state machine. Async is almost useless when you’re not incredibly I/O bound. But many people these days are because the web ate the world. C did and does have a poor almost-anything story but it doesn’t and didn’t really matter because C hasn’t been relevant on the web since 1995 or so.
- devit 3y agoFor some reason the Rust project seems to be plagued by glacial development speed, with features taking years and years to be stabilized after design and often even after initial implementation. Not sure why, progress used to be way faster years ago. For example, this "four year plan" should be implemented in 6 months at most, not 4 years. And the "long-term features" that "should be considered carefully, could not be addressed in the next few years" (lol, seriously?!?) should have serious work start right now and be released as part as the next edition in 2024 (since they need an edition break to be ergonomic). These are basic misdesigns of the type system that should have been fixed 10 years ago. Whoever is managing and paying for the Rust developers needs to fix this.
- zozbot234 3y agoBecause "stabilized" means that the feature is effectively frozen and every subsequent change must be backwards compatible. Even edition changes are restricted by the need to ensure that the vast majority of code at least can be forward-ported by automated means. So you really don't want to stabilize a new feature unless you're super confident that every part of it has the absolute best design.
- insanitybit 3y ago> For some reason the Rust project seems to be plagued by glacial development speed, with features taking years and years to be stabilized after design and often even after initial implementation. I wonder if it feels this way just because Rust has such a fast development cycle. But like, most languages takes years and years to do something like async. Rust went extremely fast, relative to other languages. > For example, this "four year plan" should be implemented in 6 months at most, not 4 years. That seems kind of insane. What language goes from idea to shipping major features in 6 months? Who even wants that?
- tuetuopay 3y ago> Not sure why, progress used to be way faster years ago. Yes, and the language was qualified as unstable for evolving so quickly. Move fast and people complain, move slow and people complain. They lose both ways. I find the development cycle to be quite good: they ship features and improvements and refinement, and I don't need to rewrite my codebase every three months because something subtly broke.
- fulafel 3y agoIs there any way async could be deprecated vs piling on more language/stdlib features?
- steveklabnik 3y agoIn theory anything could happen, sure, but that would effectively kill the largest current use-case of the language in industry. Others are catching up, but it would be an absolutely disastrous decision.
- munificent 3y ago> AsyncIterator and async generators For what it's worth, Dart has had synchronous and asynchronous generators (including `await for` statements) for as long as its had async/await. They are neat features. I've definitely written code using synchronous generators that would be hard to manually transform into a custom Iterable implementation. But they add a large amount of complexity to the language implementations and it turns out are very rarely used in practice. Here's a quick scrape I did of the most recently published 2,000 packages on our package manager: -- Style (64317 total) -- 59409 ( 92.369%): normal ================================================= 4842 ( 7.528%): async ==== 42 ( 0.065%): async* = 24 ( 0.037%): sync* = Async/await is clearly pretty useful with there being one for roughly every ten normal functions. But generators and async generators are barely used at all. Rust might be a in a different situation because efficient concurrency may be much more important in a systems language, but it's not clear to me that those same features carry their weight in Dart.
- vorticalbox 3y agoI've just used an async generator when upgrading to aws-sdk v3, the function lists all the items in a s3 bucket, if its truncated then it yeilds a recursive generator. Stops when it's got all the keys. First time in 5 years writing typescript I've actually used a generator.
- MajimasEyepatch 3y agoGenerators are one of those things that pop up more often in library code than in application code, I've found.
- no_wizard 3y agoGenerators (either variety) in JavaScript are flawed in their implementation in that they only went halfway with it. They also needed to make all primitive objects iterable as well, and/or they should have introduced iterable helpers for all data structures. The fact that you can't use an object in a `for` loop without implementing Symbol method(s) is a glaring hole in their every day utility. It makes it non-obvious to reach for in many cases The other to me is some of the semantics of generators are not well thought out, for instance, you have to call `.next()` _twice_ in order to get the first `yield` value, and how arguments to `.next()` should be used correctly is opaque. This combined with the fact `yield` doesn't follow the same lexcial scope as `this` does (IE, you can't have an arrow function yield if it was enclosed by a generator, unlike `this` which can be used inside an arrow function as reference to its enclosing scope. This would make generators far more useful IMO). Combine all this with the fact that most frameworks don't support generators natively for things like components (but they are starting to accept Promises / async functions) you end up with a relatively niche feature
- infogulch 3y agoHey boats, I found this sentence confusing to read (emphasis mine); an editing mishap perhaps? > On the other hand, there was some speculation about making “await patterns” that destructure futures and then somehow making that work here; I think this would imprudent and leaving await as an expression, and for await as a special expression for handling AsyncIterator, is the most sensible choice.
- explodingwaffle 3y agoTangential to some comments here: Why does the standard library not have block_on?
- steveklabnik 3y agoAs far as I know, the proposal to add one was determined to need an RFC: https://github.com/rust-lang/rust/pull/65875 https://github.com/rust-lang/rust/pull/65875
- devit 3y agoAnother instance of a trivial and obvious feature that can only really be designed one way that is not implemented 4 years after initial proposal.
- steveklabnik 3y agoI am not fully sure I agree with the first part of your assessment, but I share the frustration that a feature like this, which would be useful and is not particularly large, is basically ignored by the working group, while larger, more controversial, and less useful features, like keyword generics, are pursued while the more useful things languish.
- Matthias247 3y agoAs the person who proposed this feature - among lots of other ones for async Rust, like run-to-completion async functions (https://github.com/Matthias247/rfcs/blob/e7fd7042f8069e9126e2096156d2abe3c38e781b/text/0000-run-to-completion-async-fn.md https://github.com/Matthias247/rfcs/blob/e7fd7042f8069e9126e...), structured concurrency, cancellation tokens, etc) - I can unfortunately share this sentiment. It seemed really hard to land anything in async Rust since the priorities don't seem overly clear. Therefore I put a pause on all contribution attempts and went back to just using Rust as a user.
- ironhaven 3y agoblock_on is a executor that polls a future until it returns ready. If the future is not ready then the executor wait until it is woken up. If you poll a future that relies on a specific runtime (I.g. A async library that uses Tokio) to wake up the executor, with a simple block_on you will get stuck. A generic block_on in std would be a footgun for new programmers I believe.
- josephcsible 3y ago> An async generator is a natural transformation from a generator: just like functions, generators can be marked async, and now you can use the await operator inside of them. I don't see how that makes it a natural transformation.
- lambdaone 3y agoWhat makes Rust great is that its design drew upon decades of understanding of programming language theory and practice, and the designers took bold decisions based on that understanding to avoid the mistakes of other programming languages. The problem with async Rust is that the async idiom is new, and its interactions with the rest of the software ecosystem not particularly well understood. This makes async support glaringly different from the rest of the language. I'm glad the designers seem to be taking a step back and reconsidering how everything fits together.
- Rusky 3y ago> I'm glad the designers seem to be taking a step back and reconsidering how everything fits together. This is simply not what is happening here. The post is clear that this is about filling out and finishing up the plans that were laid down when async was initially designed, not changing how things fit together.
- pornel 3y agoAsync isn’t new in programming language theory. It’s a syntax sugar for state machines and continuations. I think it could be argued that PLT was already way ahead of async/await – monads are more general than futures, and Rust’s async wasn’t generalized to be an effect system. Also it’s simply not true that async wasn’t fully understood or carefully evaluated. It took years to design, and then bikeshed every detail, to the point people involved were burned out. It had multiple prototypes, and an early callback-based implementation used by hundreds of libraries, in production, for over a year. It’s probably the most thoroughly designed and tested feature in Rust’s history.
- insanitybit 3y agoTBH I think that, for the most part, I will only benefit from a few of these - mostly in terms of sugar. I routinely have positive experiences with Async rust and basically never have negative experiences/ issues that crop up because of it. In 3 years of writing Rust full time I had one async problem one time - I accidentally was causing an infinite select! loop in a tonic server, so the server would hang. Not really a big deal for 3 years of async work. I could have done the same thing in sync code just as easily tbh. I also don’t think that async-drop as quite as necessary or desirable as it may seem. I really wanted it at one point and then I realized that it’s just too tricky. It reminds me of how File calls sync_all on drop but ignores the error - ultimately, “drop” is just a really tricky place for anything complex. I’d rather see a linear type, like: struct LinearFile { fn close(self) -> Result<File, std::io::Error> { self.sync_all(); File { inner: self.inner } } } Where Linear can’t be implicitly drop’d, you have to .close() it, get a File, and File can be dropped. Hand wavy and not necessarily a good implementation but hopefully this is getting the point across. This would be preferable to shoving more into a drop impl when drop is such a constrained interface. Or maybe add a try_drop(&mut self) that will run implicity but also ? implicity. I don’t know. I guess the point is that I’m not sure an async drop can ever be worth it. TBH I feel like ~30% of people's complaints about async are solved by: a) Encouraging a sync + async API in libraries b) Adding `block_on` to the stdlib, which will help with (a) People mostly seem to care (and imo this is stupid but whatever) about using async in sync contexts when they don't want to, and they don't seem to know that you can just block.
- mplanchard 3y agoI wish that some of these folks complaining about how rust libraries force you to use async would go out and write some equivalent libraries with synchronous APIs. Rust's philosophy has always been to let library authors explore a problem space before (if ever) pulling stuff into the stdlib, and I think it is generally only beneficial for the ecosystem to have multiple ways of solving a problem.
- insanitybit 3y agoI think it goes to show that the vast majority of Rust devs don't actually care about this problem, otherwise they'd be doing that already. Like, providing a sync API over an async API is almost always as simple as: struct SyncThing { async_version: AsyncThing } impl SyncThing { pub fn sync_api(&self) { block_on(self.async_version.sync_api) } } If this were really such a huge problem I think we'd see more PRs. I get that there's some survivorship bias here, but still...
- mplanchard 3y agoThis is a really interesting post, and it's predictable if disappointing the degree to which the comments are rehashing all of the same tired arguments about async rust. I for one am pretty satisfied with async rust, and I'm excited for the stabilization of async-trait. I'd love to see some of the improvements discussed in this post come to fruition. Generators in particular are something I've found myself wanting on multiple occasions, because writing custom Iterators is relatively complex. The point about return-type notation is really interesting. Once async-trait is stabilized, we'll probably go through and rip out as much usage of the `async-trait` macro as possible, so I'll be curious to see how often we run into the issue described there. I also really like the idea highlighted in the blog post of adding async sugar to function closure types as part of expanding the support for async closures, e.g.: where F: async FnOnce() -> T // rather than where F: FnOnce() -> impl Future<Output=T> Personally, the lack of good support for async in closures remains one of my only issues with async, just because we often write code in a more "functional" style, and whenever we're dealing with complex async stuff we often wind up having to drop out of that.
- laerus 3y agoWhy yield is not a suffix operator like await?
- Georgelemental 3y ago`.await` being postfix makes it easy to chain with further method calls (eg `foo().await.bar().await`). `yield expr` returns `()`, so there is no use in chaining it.
- laerus 3y agoThanks for the explanation. I was thinking of Python's yield were it evaluates to what is .send() back in the generator.
- gaganyaan 3y agoHas there been any progress with the effects system WG? That seems quite relevant to the proposed "async gen" syntax.