Live data from Hacker News

Why asynchronous Rust doesn't work

theta.eu.org

261–270 of 499 posts

Re: Why asynchronous Rust doesn't work

#261

Earlier quoted context omitted.

In a systems context, where performance and memory ostensibly matter, why wouldn’t you want to be made aware of those inefficiencies? Sure, Go hides all that, but as a result it’s also possible to have memory leaks and spend extra time/memory on dynamic dispatch without being (fully) aware of it.

I think Rust is also able to hide certain things. Without async things are fine: type Handler = fn(Request ) -> Result , Error>; let mut map: HashMap = HashMap::new(); map.insert("/", |req| { Ok(Response::new("hello".into())) }); map.insert("/about", |req| { Ok(Response::new("about".into())) }); Sure, using function pointer `fn` instead of one of the Fn traits is a bit of a cheating, but realistically you wouldn't wa…

>It would be reasonable to suggest that if the first one is flexible enough to be stored in a container without any fuss, then the second one should as well

I don't think this a reasonable in Rust (or in C/C++). I 90% of the pain of futures in Rust is most users don't want to care about memory allocation and want Rust to work like JS/Scala/C#.

When using a container containing a function, you only have to think allocating memory for the function pointer, which is almost always statically allocated. However for an async function, there's not only the function, but the future as well. As a user the language now poses a problem to you, where does the memory for the future live.

1. You could statically allocate the future (ex. type Handler = fn(Request) -> ResponseFuture, where ResponseFuture is a struct that implemented Future).

But this isn't very flexible and you'd have to hand roll your own Future type. It's not as ergonomic as async fn, but I've done it before in environments where I needed to avoid allocating memory.

2. You decide to box everything (what you posted).

If Rust were to hide everything from you, then the language could only offer you (2), but then the C++ users would complain that the futures framework isn't "zero-cost". However most people don't care about "zero-cost", and come from languages where the solution is the runtime just boxes everything for you.

Re: Why asynchronous Rust doesn't work

#262
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…

> people also thought garbage collection was impossible in a systems language until Rust solved it No, they didn't. Linear typing for systems languages had already been done in ats, cyclone, and clean, the latter two of which were a major inspiration for rust. Venturing further into gc territory: long before rust was even a twinkle in graydon hoare's eye, smart pointers were happening in c++, and apple was experiment…

Apple wasn't experimenting with Objective-C for drivers, NeXTSTEP drivers were written in Objective-C.

macOS IO Kit replacement, Driver Kit, is an homage to NeXTSTEP Driver Kit name, the Objective-C framework.

Re: Why asynchronous Rust doesn't work

#263

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…

In all this time, maestro Andrei Alexandrescu was right when he said Rust feels like it "skipped leg day" when it comes to concurrency and metaprogramming capabilities. Tim Sweeney was complaining about similar things, saying about Rust that is one step forward, one step backward. These problems will be evident at a later time, when it will be already too late. I will continue experimenting with Rust, but Zig seems t…

And Zap (scheduler for Zig) is already faster than Tokio.

Zig and other recent languages have been invented after Rust and Go, so they could learn from them, while Rust had to experiment a lot in order to combine async with borrow checking.

So, yes, the async situation in Rust is very awkward, and doing something beyond a Ping server is more complicated than it could be. But that’s what it takes to be a pioneer.

Re: Why asynchronous Rust doesn't work

#264

Earlier quoted context omitted.

>There's a difference between "we decided this 3 years ago" and "we rushed the decision". As far as I understand the situation, the completion-based API simply was not on the table 3 years ago. io-uring was not a thing and there was a negligible interest in properly supporting IOCP. So when a viable alternative has appeared right before stabilization of the developed epoll-centric API, the 3 year old decision has not…

What happens if you drop the task between 1 and 2? Does dropping block until the cancellation of both tasks is complete?

As I've mentioned several times, in this model you can not simply "drop the task" without running its asynchronous Drop. Each state in FSM will be generated with a "drop" transition function, which may include asynchronous cancellation requests (i.e. cleanup can be bigger than one transition function and may represent a mini sub-FSM). This would require introducing more fundamental changes to the language (same as with proper self-referential types) be it either some kind of linear type capabilities or a deeper integration of runtimes with the language (so you will not be able to manipulate FSM states directly as any other data structure), since right now it's safe to forget anything and destructors are not guaranteed to run. IMO such changes would've maid Rust a better language in the end.

Re: Why asynchronous Rust doesn't work

#265
post #214
post #132

This is pretty overblown. I write async rust every day for my job, just fine, with no real problems. Probably because I'm consuming other libraries, I'm not trying to write my own. I use well-tested libraries like Actix-Web or occasionally Tokio. I've migrated multiple projects from futures to async/await once the syntax came out. ' The problems the author is describing might apply more to library authors, but for th…

> The problems the author is describing might apply more to library authors I don't know much about Rust, but I found the examples in the article kind of simple . As far as I can see, the author is trying to spawn a new thread and share an object between the main thread and the new one. It's seems like an everyday paradigm. Perhaps the problems arise when passing complex objects instead of native types (i.e. u32).

> As far as I can see, the author is trying to spawn a new thread and share an object between the main thread and the new one. It's seems like an everyday paradigm.

And it works very well in Rust as long as you understand the core concept of the language (ownership). Rust is special, it has this fundamental concept that you need to understand before going on, and that's add some inevitable learning curve. But once you know how it works, it just works exactly as you'd expect and in the kinds of things the author is trying to do[1], it doesn't gets in your way at all.

The post just sounds like the author didn't took the time to understand the core concepts, and is trying to brute-force their way through. This way of learning doesn't work very well for Rust, or at least not if you are impatient. (Especially because the error messages when it comes to closures are still far from Rust's overall standards).

[1] there are things where Rust's ownership is a constraint (cyclic datastrutures for instance, or shared memory between your program and a C program (io_uring, or wayland)). But those are situational.

Re: Why asynchronous Rust doesn't work

#266

If Rust had to be rewritten from scratch today, it would probably be done differently, learning from experience since it was invented. Cargo, macros, const generics and `#[cfg]` wouldn’t exist any more, replaced with comptime evaluation. The standard library would be half its current size (considering its redundant functions, deprecated functions, or constructions that Clippy wants to be replaced with other construct…

> [async] “colorless” I’m super interested that you think that’s something that can exist, how would it work, what would it look like in your mind ?

Zig does it (https://kristoff.it/blog/zig-colorblind-async-await/).

Basically using "await" just means you are declaring opportunity for concurrency at the call site, but it is still valid to await a synchronous function. And it is valid to call a function synchronously, even if it internally supports concurrency via await.

Re: Why asynchronous Rust doesn't work

#267

If Rust had to be rewritten from scratch today, it would probably be done differently, learning from experience since it was invented. Cargo, macros, const generics and `#[cfg]` wouldn’t exist any more, replaced with comptime evaluation. The standard library would be half its current size (considering its redundant functions, deprecated functions, or constructions that Clippy wants to be replaced with other construct…

> [async] “colorless” I’m super interested that you think that’s something that can exist, how would it work, what would it look like in your mind ?

I think the GP refers to Zig's approach[1], which is just “do mark functions as sync or async in there signature, just switch between two modes at compile-time using a global flag”, which I don't think fits well into Rust's values of being explicit about what is happening.

[1]: https://kristoff.it/blog/zig-colorblind-async-await/

Re: Why asynchronous Rust doesn't work

#268
post #50
post #39

> And, as I said at the start, that makes me kinda sad, because I do actually like Rust. I think that’s the most important part of the article. People like Rust but it’s becoming more complex than C++. But unlike C++ it’s more difficult to pick and choose what you use. Rust’s death will be one by thousand cuts. “I really like the language but can’t justify all that complexity in my new small and simpke project” is wh…

It is in no danger of become as complex than C++. Nowhere near.

Traits, generics, lifetimes, macros and endless, and incomprehensible backtraces when using Futures already makes it more complex.

Rust is difficult to read and maintain, due to the common use of abstractions over abstractions over abstractions. Just looking at a structure, it’s already hard to understand what functions are available (hidden in traits, themselves behaving different according to cargo features…). And of course, it also has things such as operator overloading, and hidden heap memory allocations, so there’s a lot of hidden control flow happening.

It’s also even slower than C++ to compile, especially when using macros. And it doesn’t have static analysis too as C++ does. Both of these also contribute to “complexity” as in “how much effort it takes to write an application”.

Re: Why asynchronous Rust doesn't work

#269

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…

This post is completely and totally wrong. At least you got to ruin my day, I hope that's a consolation prize for you.

There is NO meaningful connection between the completion vs polling futures model and the epoll vs io-uring IO models. comex's comments regarding this fact are mostly accurate. The polling model that Rust chose is the only approach that has been able to achieve single allocation state machines in Rust. It was 100% the right choice.

After designing async/await, I went on to investigate io-uring and how it would be integrated into Rust's system. I have a whole blog series about it on my website: https://without.boats/tags/io-uring/. I assure you, the problems it present are not related to Rust's polling model AT ALL. They arise from the limits of Rust's borrow system to describe dynamic loans across the syscall boundary (i.e. that it cannot describe this). A completion model would not have made it possible to pass a lifetime-bound reference into the kernel and guarantee no aliasing. But all of them have fine solutions building on work that already exists.

Pin is not a hack any more than Box is. It is the only way to fit the desired ownership expression into the language that already exists, squaring these requirements with other desireable primitives we had already committed to shared ownership pointers, mem::swap, etc. It is simply FUD - frankly, a lie - to say that it will block "noalias," following that link shows Niko and Ralf having a fruitful discussion about how to incorporate self-referential types into our aliasing model. We were aware of this wrinkle before we stabilized Pin, I had conversations with Ralf about it, its just that now that we want to support self-referential types in some cases, we need to do more work to incorporate it into our memory model. None of this is unusual.

And none of this was rushed. Ignoring the long prehistory, a period of 3 and a half years stands between the development of futures 0.1 and the async/await release. The feature went through a grueling public design process that burned out everyone involved, including me. It's not finished yet, but we have an MVP that, contrary to this blog post, does work just fine, in production, at a great many companies you care about. Moreover, getting a usable async/await MVP was absolutely essential to getting Rust the escape velocity to survive the ejection from Mozilla - every other funder of the Rust Foundation finds async/await core to their adoption of Rust, as does every company that is now employing teams to work on Rust.

Async/await was, both technically and strategically, as well executed as possible under the circumstances of Rust when I took on the project in December 2017. I have no regrets about how it turned out.

Everyone who reads Hacker News should understand that the content your consuming is usually from one of these kinds of people: a) dilettantes, who don't have a deep understanding of the technology; b) cranks, who have some axe to grind regarding the technology; c) evangelists, who are here to promote some other technology. The people who actually drive the technologies that shape our industry don't usually have the time and energy to post on these kinds of things, unless they get so angry about how their work is being discussed, as I am here.

Re: Why asynchronous Rust doesn't work

#270

Earlier quoted context omitted.

I think Rust is also able to hide certain things. Without async things are fine: type Handler = fn(Request ) -> Result , Error>; let mut map: HashMap = HashMap::new(); map.insert("/", |req| { Ok(Response::new("hello".into())) }); map.insert("/about", |req| { Ok(Response::new("about".into())) }); Sure, using function pointer `fn` instead of one of the Fn traits is a bit of a cheating, but realistically you wouldn't wa…

> It would be reasonable to suggest that if the first one is flexible enough to be stored in a container without any fuss, then the second one should as well I don't think this a reasonable in Rust (or in C/C++). I 90% of the pain of futures in Rust is most users don't want to care about memory allocation and want Rust to work like JS/Scala/C#. When using a container containing a function, you only have to think allo…

Thanks for the suggestion. I didn't think of (1), although it's a pity that it's not as ergonomic as async fn.

I kinda feel like there's this false dichotomy here: either hide and be like Java/Go or be as explicit as possible about the costs like C/C++. Is there maybe a third option, when I as a developer aware of the allocation and dispatch costs, but the compiler will do all the boilerplate for me. Something like `async dyn fn(Request) -> Result`? :)

Post reply on HN