7 ms·
Async 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
by inamberclad 3y ago
Async 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.