Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> 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 use async, Rust is attempting to solve the async problem with the same guarantees it has for blocking code. Turns out that is hard.



>It doesn't! No one is forcing anyone to use async.

Well, hang on there... this isn't entirely fair.

Rust does not force you to be async, but the community in some ways pushes you to be async even where it might not make sense for it to be the default. I can't say for certain (and this is all my opinion, to be clear) but it feels like this started happening when async-fervor hit its peak.

My go-to example is reqwest, which if you want a blocking HTTP call, still just needs all of Tokio in the background. I find it really odd that the blocking API is just a wrapper for a finagled async API; if I'm choosing the blocking one, I probably don't want Tokio in my project.

There are other HTTP request libraries, to be clear - but they're often less battle tested and/or have their own lurking bugs. Reqwest is the de-facto one and it'd be nice to be able to use it without the heaviness it brings in.

Depending on the domain you're programming in, it can often feel like async-by-default is the norm. It can be frustrating in Rust.

(It's nowhere near enough to deter me from using the language, mind you)


That is an absolutely fair distinction.

I think a blocking implementation being a `await(async impl)` is typically fine...

the problem with that is the lack of async interop in the current state of things, more than the async runtime which mostly gets compiled away.


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<(), IOClient::Error> {
         /// go and get the data
         let n1_fut : IOClient::Future<u64> = self.io.sql_async("SELECT n1 FROM table1", &[])?;
         let n2_fut : IOClient::Future<u64> = self.io.sql_async("SELECT n2 FROM table2", &[])?;
         let n3_fut : IOClient::Future<u64> = 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<IOClient::HTTPStatus> = 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<(), IOClient::Error> {
         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();
   }


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.


This code is just an example. The state machine can easily run in its own thread, separate from the main thread. As long as the state machine had an IOClient instance that lets it send I/O requests to the IOServer and receive I/O results, you're good. Also, you could imagine an IOClient having an API that takes a StateMachine instance as input, and returns an IOClient::Future that resolved to the machine's main() return value.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: