Live data from Hacker News

Why asynchronous Rust doesn't work

eta.st

231–240 of 305 posts

Re: Why asynchronous Rust doesn't work

#231
post #199

"Maybe we could just have kept Rust as it was circa 2016, and let the crazy non-blocking folks write hand-crafted epoll() loops like they do in C++. I honestly don’t know, and think it’s a difficult problem to solve." I think this is the most underappreciated part of this article. Since when did everything have to be async? There are other ways to represent concurrency that more accurately reflect what the computer i…

> let the crazy non-blocking folks write hand-crafted epoll() loops like they do in C++. > I think this is the most underappreciated part of this article. I think it's incredibly silly actually. Abandon all async for a difficult and error prone epoll model? > Since when did everything have to be async? It doesn't! No one is forcing anyone to use async. I'm not sure why the author implies that. But if you do want to u…

I'm not saying that we should go back to hand-rolling our own epoll loops. I'm saying that we can do better than async/await by making both the state machines and event loop explicit. For example, here's an API I'd prefer to use over async/await:

   /// A state machine that adds three numbers and uploads them to a web server
   struct AddAndUpload {
      /// I/O handle to the event loop
      io: IOClient,
      /// Buffer to store numbers I load
      nums: [u64; 3],
      /// URL to upload the data to
      url: String
   }

   impl AddAndUpload {
      /// Constructor
      pub fn new(io: IOClient, url: String) -> AddAndUpload {
         AddAndUpload {
            io,
            nums: [0u64; 3],
            url
         }
      }

      /// Entry point to this state machine
      pub fn inner_main(&mut self) -> Result {
         /// go and get the data
         let n1_fut : IOClient::Future = self.io.sql_async("SELECT n1 FROM table1", &[])?;
         let n2_fut : IOClient::Future = self.io.sql_async("SELECT n2 FROM table2", &[])?;
         let n3_fut : IOClient::Future = self.io.sql_async("SELECT n3 FROM table3", &[])?;

         // wait for all I/O operations to finish
         IOClient::wait_all(&[&n1_fut, &n2_fut, &n3_fut])?;

         // extract results
         let n1 = n1_fut.into_inner();
         let n2 = n2_fut.into_inner();
         let n3 = n3_fut.into_inner();

         // upload them
         let sum = n1 + n2 + n3;
         let upload_fut : IOClient::Future = self.io.http_post_async(&self.url, &["content-type: application/octet-stream"], &sum.to_be_bytes())?;

         let upload_http_status = upload_fut.wait()?.into_inner();

         match upload_http_status.as_u16() {
            200 => {
               Ok(())
            }
            400..499 => {
               Err(IOClient::Error::Custom("client error"))
            }
            500..599 => {
               Err(IOClient::Error::Custom("server error"))
            }
            x => {
               Err(IOClient::Error::Custom("Nonsensical HTTP code"))
            }
         }
      }
   }

   impl IOClient::StateMachine for AddAndUpload {
      type Return = ();
      fn main(&mut self) -> Result {
         self.inner_main()
      }
   }

   /\* somewhere else \*/

   fn main() {
       let io_server = IOServer::spawn().unwrap();
       let io_client = io_server.client().unwrap();
       let add_and_upload = AddAndUpload::new(io_client, "http://example.com".to_string());
       loop {
         io_server.run().unwrap();
         match add_and_upload.get_machine_status() {
            Ok(IOClient::StateMachine::Finished(result)) => {
               eprintln!("add_and_uploaded exited with {:?}", &result);
               break;
            }
            Ok(_) => {},
            Err(e) => {
               panic!("add_and_upload aborted: {:?}", &e);
            }
         }
      }
      io_server.terminate();
   }

Re: Why asynchronous Rust doesn't work

#232
post #143
post #44

> As someone who used to really love Rust, this makes me quite sad. The current async story is still an MVP and I too dislike it. In the months before async, the ecosystem seemed on halt, waiting for async to land on stable Rust. Since then nothing has changed. The ecosystem "degraded" noticeable and has not recovered since. Maybe in future async will be great, but right now I try to avoid it. ... I still love Rust

Sorry to spoil your axe-grinding with hard data, but the Rust async ecosystem has exploded since 2019. It is now about 5-6 times larger than it was before async/await landing: https://lib.rs/crates/tokio/rev And Rust as a whole keeps growing exponentially: https://lib.rs/stats

That's not what I was talking about. Before async we had one ecosystem. Now we have at last three: non-async, Tokio, async-std

My point is, the ecosystem experience has degraded since the async MVP is stabel.

Re: Why asynchronous Rust doesn't work

#233
post #106

Earlier quoted context omitted.

Naive synchronous reference counting can lead to large pauses as well. What happens when you drop the last reference to the root of a 10,000-node search tree? You do 10,000 reference deferments and free()s. Reference counting might feel more incremental than GC, but really is not. There are tricks you can use, but you're better off with a fast, modern, pauseless real GC that comes with tons of other benefits. Look: m…

> Naive synchronous reference counting can lead to large pauses as well. What happens when you drop the last reference to the root of a 10,000-node search tree? > You do 10,000 reference deferments and free()s. You do all that work at the precise point where the last reference was dropped . What people who complain about GC pauses dislike is the GC causing pauses in completely unrelated threads, including these threa…

> You do all that work at the precise point where the last reference was dropped.

True, but rarely meaningful in practice. People don't anticipate spikes at implicit deallocation sites. On the contrary, they typically use RCs to share data without worrying about exact destructure time (i.e. same reason as GC), and thus will be caught off guard in either case. In fact so much so that even experts frequently introduce accidental RC cycles (causing hard-to-debug leaks).

In the cases where you actually want destruction to be deterministic (e.g. large allocations, graceful teardown, kernel resources etc), neither a traditional GC nor RCs are a good solution.

The simplicity and elegance of Rusts RAII scoped ownership model largely goes away with async, due to the unavoidable "Arc-hell".

Re: Why asynchronous Rust doesn't work

#234
post #231

Earlier quoted context omitted.

> let the crazy non-blocking folks write hand-crafted epoll() loops like they do in C++. > I think this is the most underappreciated part of this article. I think it's incredibly silly actually. Abandon all async for a difficult and error prone epoll model? > Since when did everything have to be async? It doesn't! No one is forcing anyone to use async. I'm not sure why the author implies that. But if you do want to u…

I'm not saying that we should go back to hand-rolling our own epoll loops. I'm saying that we can do better than async/await by making both the state machines and event loop explicit. For example, here's an API I'd prefer to use over async/await: /// A state machine that adds three numbers and uploads them to a web server struct AddAndUpload { /// I/O handle to the event loop io: IOClient, /// Buffer to store numbers…

okay, but that doesnt solve basically the main thing that async paradigms seek to solve: sharing of resources between waiting disjoint processes.

your statemachine blocks the thread. if you had a more complicated state machine, maybe nested machines, theyd block each other because they dont know how to cooperate.

Re: Why asynchronous Rust doesn't work

#236

Earlier quoted context omitted.

Some of the pain inflicted by async Rust is incidental, not due to this GC choice. Pin is the biggest culprit. Pin exists so that references captured in Futures may be implemented via raw pointers. This implies that a Future contains pointers to itself, hence Pin. The cost of Pin is that it forces you to write unsafe code as a matter of course , the so-called pin-projections. [1] Look at the requirements for structur…

I don't think base + offset would have worked because then references would have to be codegen'd differently depending on whether they're in a future or not; this would have impacted separate compilation as you can pass references to other functions (including across FFI). Relocations only work at load time (either dynamic linking or link time), so they wouldn't work. Limiting what can be captured was tried with the…

"Relocations" was meant as an analogy. The idea is, each time a future is entered, it checks if its base pointer has changed. If so, it fixes up its own pointers, similar to how a linker fixes up self-pointers in a dynamic library. There's obvious limitations but it would handle common cases efficiently.

Anyways when I first tried async, I encountered structural pinning immediately, and I was surprised to learn that users are just expected to write unsafe code to participate in this system. Maybe it really is the best tradeoff but seems at odds with the rest of the language.

Re: Why asynchronous Rust doesn't work

#237

Earlier quoted context omitted.

From my very limited expeirience with Rust I noticed that it becomes way more easy and laid back language when you just skip using references and lifetimes nearly completely and just wrap everything in Rc . Then you are getting expeirience of fairly high level language with a lot of very cool constructs and features like exhaustive pattern matching and value types with a lot of auto-derived functionality. Does Rc hel…

You can get those cool features in languages such as OCaml/F#/Scala/Haskell, and then you don't have to worry about managing memory because you have a GC.

Sure but all of them are weird. Rust is something you can pick up in an afternoon if you have expeirience in C++-like languages.

Re: Why asynchronous Rust doesn't work

#238

Earlier quoted context omitted.

I'm holding out for formal-methods based static analysis on lifetimes in zig.

Not possible for the same reasons why just adding a borrow checker to C++ isn't possible. Mostly this comes down to incompatibility with existing code. Zig is just not memory safe, and the only solution would be to add a GC.

If you don't consider Swift style Automatic Reference Counting a GC, then there is another option.

Re: Why asynchronous Rust doesn't work

#239
post #157

Earlier quoted context omitted.

I wonder if you could just pass this 10000 tree node root to another thread that will just drop it. This should result in no pause for main thread. Apparently you need Arc for that not Rc which shouldn't have much overhead over Rc in reasonable scenarios.

Yes, that is how C++/WinRT handles cascading deletion of COM instances, however when you are going down that route, it is basically a poor man's tracing GC.

However it just for the stuff you need it for. It's not all encompassing. It's not mandatory part of the language.

Re: Why asynchronous Rust doesn't work

#240
post #193

Earlier quoted context omitted.

I considered mentioning "Go", and while it would be nice to have a Go with generics + sum types, it's also very nice having Go as it is today. In other words, it would be nice to have a "Go with those things" and a "Go without them" (predictable rebuttal: "But if Go supports those things, you would only have to use them when you wanted to!" Frankly though, the type system is absurdly overemphasized. Squabbling over t…

Java (and other JVM languages), as well as C# to a certain extent address the points you raise. Secondly, it's not true that golang has a minimal learning curve (I've seen senior engineers write bad golang code when they're onboarded - it takes time to learn the golang way of doing things and its quirks), and it's not even a fundamental goal to have.

I disagree. Firstly, it’s widely agreed upon that Go has quite a lot lower learning curve than just about any other language. Your senior engineer anecdotes sound like outliers.

With respect to Java and C#, I addressed a similar question here: https://news.ycombinator.com/item?id=29181361

Even where those languages have nominally improved, it is often so difficult in practice that virtually no one bothers to use the improvements. For example, while Java and .Net technically can do AOT, static compilation, virtually no one does, instead preferring to put up with runtime dependencies (including the runtime itself at a minimum). It’s ticking a box so on paper they compare better to Go (or Rust) but the practical experiences remain leagues apart.

Post reply on HN