Live data from Hacker News

Why asynchronous Rust doesn't work

theta.eu.org

171–180 of 499 posts

Re: Why asynchronous Rust doesn't work

#171
post #13

Async isn't really the problem - the same issue pops up with error handling, with resource management, with anything where you want to pass functions around. The real problem is that Rust's ownership semantics and limited abstractions mean it doesn't really have first-class functions: there are three different function types and the language lacks the power to abstract over them, so you can't generally take an expres…

Rust programs can theoretically be fast, but most of the ones I've used are slow. I tried two high profile implementations of the same type of software, one in Rust and one in Java. The Java one was faster and used less memory.

Rust programmers tend to do all kinds of little hacks here and there to make the borrow checker happy. It can add up. The borrow checker is perfectly happy when you copy everything.

Rust is becoming one giant antipattern.

(I'm sure highly experienced Rust programmers can get it to work, but there are probably less than 1000 people on this planet that can write good Rust that outperforms C++ so does it really count.)

Re: Why asynchronous Rust doesn't work

#172
post #88

Earlier quoted context omitted.

> I'm more aware of procedural language origins of syntactic async/await than functional? The scala proposal in 2016 for async/await even cites C#'s design (which came in C# 5.0 in 2012) as an inspiration[1]. The C# version comes from F# (2007) which was in turn inspired by the "Poor Man's Concurrency Monad" implementation for Haskell (1999) (in turn inspired by Concurrent Haskell and, ultimately, Concurrent ML). It'…

I wasn't aware of the F# heritage, that's interesting. I'm curious why the scala proposal wouldn't cite it. Especially surprising since scala's at least superficially looks more like F#'s than it does like C#'s. I don't dispute (as I have had to say repeatedly in other branches of this) that the roots of futures as a concept are in functional programming, but the path I'm saying I see here is effectively: - haskell/m…

I don't see async/await (at least when built on top of futures/promises) as a procedural thing - the parts of C# where it's used are the least procedural parts of C#, and Python has always been multi-paradigm. I'd say it's mainly a way of doing monadic futures in languages that don't have monads (mainly because of lacking HKT) - hence why F# adopted it first, and then it made its way into functional-friendly languages that either didn't have HKT, or in Scala's case found it to be a useful shortcut anyway. "Functional" is a spectrum rather than a binary, but I don't think it's right to see async/await as being imperative any more than having map/reduce/filter in C#/Python/Javascript makes them an imperative thing. (I would agree that Haskell and Scala, with true monads, are more functional than C#/Python/Javascript - but I'd say that having async/await means C#/Python/Javascript are closer to Haskell/Scala than similar languages that don't have async/await; async/await are making them more functional, not less).

As for why I mentioned Scala specifically, I understand that Rust's Futures are directly based on Scala's. I had assumed this would apply to async/await as well, but it sounds like apparently not? In any case there's not a huge difference between the Scala/C# versions of the concept AFAICS.

Re: Why asynchronous Rust doesn't work

#173
post #167

A bigger problem in my opinion is that Rust has chosen to follow the poll-based model (you can say that it was effectively designed around epoll), while the completion-based one (e.g. io-uring and IOCP) with high probability will be the way of doing async in future (especially in the light of Spectre and Meltdown). Instead of carefully weighing advantages and disadvantages of both models, the decision was effectively…

Why did polling have to be baked into the language? Seems bizarre for a supposedly portable language to assume the functionality of an OS feature which could change in the future. Meanwhile C and C++ can easily adopt any async system call style because it made no assumptions in the standards about how that would be done. Rust also didn't solve the colored functions problem. Most people think that's an impossible prob…

>Why did polling have to be baked into the language?

See this comment: https://news.ycombinator.com/item?id=26407440

>Meanwhile C and C++ can easily adopt any async system call style because it made no assumptions in the standards about how that would be done.

Do you know about co_await in C++20? AFAIK (I only have a very cursory knowledge about it, so I may be wrong) it also makes some trade-offs, e.g. it requires allocations, while in Rust async tasks can live on stack or statically allocated regions of memory.

Also do not forget that Rust has to ensure memory safety at compile time, while C++ can be much more relaxed about it.

Re: Why asynchronous Rust doesn't work

#174
post #2

I'm coming around to the position that pure "async" is OK, and pure threading is OK, and green threads (as in Go goroutines) are OK, but having more than one of those in a language is not OK. They do not get along well.

The problem with green threads is that, unless they're a part of the platform ABI, they don't really get along with anything else - including green threads in other languages/frameworks! This makes cross-language/runtime interop unnecessarily difficult, and as I understand, it's partly why Go apps tend to be "pure Go", and even their stdlib insists on using syscalls on platforms where you're supposed to go via libc (like BSDs, and previously also macOS).

But there doesn't seem to be any concerted effort to standardize green threads. Win32 has had fibers for a while, but nobody's actually using them, and runtimes generally don't support them. Even .NET tried to do it in 1.x, but then dropped support in later versions.

Re: Why asynchronous Rust doesn't work

#175

> Was spinning up a bunch of OS threads not an acceptable solution for the majority of situations? Could we have explored solutions more like Go, where a language-provided runtime makes blocking more of an acceptable thing to do? I think this is the real fundamental disagreement here (well, at least with the async stuff). (And the comments slightly earlier, regarding "the color of your function".) The way I think abo…

Rust async doesn't quite solve the cancellation problem either.

Sure, you can stop polling the future at any time, but that doesn't mean that you left the system state consistent.

Careful thought is needed on every call to await in an async function. It might be the last thing that async function ever does.

That's probably something that should be true in a well-implemented system anyway. After all, processes could be killed at any point.

Re: Why asynchronous Rust doesn't work

#176
Can we please stop using this "color" argument to Rust? The original colouring article was about JavaScript, a dynamically typed language. But rust is a statically typed language, and in a sense, the type is the colour. You can't return an error from a function that does not return a Result. And most people agree it is a great thing over dynamic/implicit exceptions. Declaring a function just changes the return type to return a future instead.

Also, it's been many year Pin was stabilized, and that you can write `impl Fn(Xx)` instead of using a where clause, and return closures using that syntax.

Re: Why asynchronous Rust doesn't work

#177
post #13

Async isn't really the problem - the same issue pops up with error handling, with resource management, with anything where you want to pass functions around. The real problem is that Rust's ownership semantics and limited abstractions mean it doesn't really have first-class functions: there are three different function types and the language lacks the power to abstract over them, so you can't generally take an expres…

> Async isn't really the problem - the same issue pops up with error handling, with resource management

There are ways to handle that, though. It's called algebraic effects and they are nicely implemented in Unison language[1] (although there they're called abilities[2]). It's very interesting language; I recommend reading [3] for good overview.

[1] https://www.unisonweb.org/

[2] https://www.unisonweb.org/docs/abilities

[3] https://jaredforsyth.com/posts/whats-cool-about-unison/

Re: Why asynchronous Rust doesn't work

#178
post #167

Earlier quoted context omitted.

Why did polling have to be baked into the language? Seems bizarre for a supposedly portable language to assume the functionality of an OS feature which could change in the future. Meanwhile C and C++ can easily adopt any async system call style because it made no assumptions in the standards about how that would be done. Rust also didn't solve the colored functions problem. Most people think that's an impossible prob…

>Why did polling have to be baked into the language? See this comment: https://news.ycombinator.com/item?id=26407440 >Meanwhile C and C++ can easily adopt any async system call style because it made no assumptions in the standards about how that would be done. Do you know about co_await in C++20? AFAIK (I only have a very cursory knowledge about it, so I may be wrong) it also makes some trade-offs, e.g. it requires a…

C++20 coroutines are not async in the standard. They are just coroutines. Actually they have no implementation. The user has to write classes to implement the promise type and the awaitable type. You could just as easily write a coroutine library wrapping epoll as you could io_uring. The only thing it does behind your back (other than compile to stackless coroutines) is allocate memory, which also goes for a lot of other things.

Re: Why asynchronous Rust doesn't work

#179
post #60
post #49

Earlier quoted context omitted.

There wasn't any multi-threading in that example I think.

Yes, there was: the function was calling the passed in closure from a different thread. It is just one thread that’s actually appending to the Vec, but it’s still a multi-threaded example.

Are we talking about the same example?

  struct Database {
    data: Vec
  }
  impl Database {
     fn store(&mut self, data: i32) {
         self.data.push(data);
     }
  }

  fn main() {
    let mut db = Database { data: vec![] };
    do_work_and_then(|meaning_of_life| {
        println!("oh man, I found it: {}", meaning_of_life);
        db.store(meaning_of_life);
    });
    // I'd read from `db` here if I really were making a web server.
    // But that's beside the point, so I'm not going to.
    // (also `db` would have to be wrapped in an `Arc>`)
    thread::sleep_ms(2000);
  }
No threads are spawned.

Re: Why asynchronous Rust doesn't work

#180
post #67

Earlier quoted context omitted.

FWIW I've done a fair bit of researching with io_uring. For file operations it's fast, bit over epoll the speedups are negligible. The creator is a good guy but they're having issues with the performance numbers being skewed due to various deficiencies in the benchmark code, such as skipping error checks in the past. Also, io_uring can certainly be used via polling. Once the shared rings are set up, no syscalls are n…

We've briefly been playing with io_uring (in async rust) for a network service that is CPU-bound and seems to be bottlenecked in context switches. In a very synthetic comparison, the io_uring version seemed very promising (as in "it may be worth rewriting a production service targeting an experimental io setup"), we ran out of the allocated time before we got to something closer to a real-world benchmark but I'm fair…

Yes, I should have specified - in theory io_uring is much faster and less resource intensive. With the right polish, it can certainly be the next iteration of I/O syscalls.

That being said, you have to restructure a lot of your application in order to be io_uring ready in order to reap the most gains. In theory, you'll also have to be a bit pickier with CPU affinities, namely when using SQPOLL (submit queue poll), which creates a kernel thread. Too much contention means such facilities will actually slow you down.

The research is changing weekly and most of the exciting stuff is still on development branches, so tl;dr (for the rest of the readers) if you're on the fence, best stick to epoll for now.

Post reply on HN