Live data from Hacker News

Why asynchronous Rust doesn't work

theta.eu.org

451–460 of 499 posts

Re: Why asynchronous Rust doesn't work

#451
post #53

Earlier quoted context omitted.

Could Rust switch? More importantly, would a completion based model alleviate the problems mentioned?

Without introducing Rust 2? Highly unlikely. I should have worded my message more carefully. Completion-based model is not a silver bullet which would magically solve all problems (though I think it would help a bit with the async Drop problem). The problem is that Rust async was rushed without careful deliberation, which causes a number of problems without a clear solution in sight.

There exists actually a proposal for adding completion based futures at [1], which is compatible to what exists now and certainly doesn't require a Rust 2. It will however certainly increase the language surface area.

[1] https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-asy...

Re: Why asynchronous Rust doesn't work

#452
post #241
post #140

Earlier quoted context omitted.

It's not about "abstracting away" ownership, it's about being polymorphic over it. I want to keep the distinction between Fn, FnOnce, and FnMut. But I want to be able to write a `compose` function that works on all three, returning the correct type in each case. That's not taking away from my ability to manage ownership, it's letting me abstract over it.

You could do the polymorphic return with an enumeration. But those are distinct types with very different semantics: you can’t just say “it returns a function that you can call once or maybe a function you can call more than once. Shrug”.

> But those are distinct types with very different semantics: you can’t just say “it returns a function that you can call once or maybe a function you can call more than once. Shrug”.

Right, that's why I want parametricity i.e. HKT. String and Int are different types with very different semantics, you can't just say "it returns a String or maybe an Int, shrug", but it's very useful to be able to write generic code and datastructures (e.g. collections) that work for String and Int.

Re: Why asynchronous Rust doesn't work

#453
post #171

Earlier quoted context omitted.

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 be…

> 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. Do you mind sharing an example? If you're talking about using to_owned or clone without reason, then it's fully on the developer. Some more pitfalls to avodi: https://llogiq.github.io/2017/06/01/perf-pitfalls.html What you say definitel…

> If you're talking about using to_owned or clone without reason, then it's fully on the developer.

Well, if we could just make developers smarter then everything would be easy, but we can't. It's reasonable to ask whether real-world developers working in Rust end up doing enough extra copying to outweigh the overhead of a JVM-like garbage collector that would let them avoid ever manually copying.

Re: Why asynchronous Rust doesn't work

#454

Earlier quoted context omitted.

Which problem are we referring to? I was only making a general statement that I think Rust would have benefited from HKTs instead of doing umpteen ad-hoc implementations of specific higher-kinded types. I'm far from an expert, so please correct me if I'm wrong: Wouldn't HKTs help us abstract over function/types more easily, including closures? Aren't GATs a special case of HKTs? If Rust somehow had HKTs and "real" mo…

> Wouldn't HKTs help us abstract over function/types more easily, including closures? In the sense that HKTs are a higher level abstraction, sure. More later. > Aren't GATs a special case of HKTs? My understanding is that GATs can get similar things done to HKTs for some stuff that Rust cares about, but that doesn't give them a subtyping relationship. Haskell has both higher kinded types and type families. That being…

> That is, even if Rust had a monad trait, that does not mean that Try and Future could both implement it. This is because, in Haskell, these things have the same signatures. In Rust, they do not have the same signature. For reference:

This is nonsense; Try and Future are not the same thing in Haskell, there are plenty of functions you can only call with one or the other.

The point of the monad abstraction is to abstract over the part that is the same, mostly the function which Rust calls and_then:

    pub fn and_then(self, f: F) -> AndThen
    where
    F: FnOnce(Self::Item) -> B,
    B: IntoFuture,
    Self: Sized, 
    
    pub fn and_then(self, op: F) -> Result
    where
    F: FnOnce(T) -> Result,
Obviously these signatures aren't quite identical, but they're actually even more similar than I thought; AndThen is a subtype of `impl Future`, and the fact that there's no IntoResult seems like plumbing rather than anything fundamental. So if we could write an interface like:

    trait Monad> {
      pub fn and_then(self: M, f: F) -> impl M
      where
      F: FnOnce(A) -> M
    }
then these both conform to that - in the first case with M=Future and A=Self::Item, in the second case with M=Result and A=T.

Yes, there are low-level things you might want to do with Future or Result that you can't do via the monad interface - just as with any other high-level interface. But having the high-level interface available makes the simple, common cases a lot easier. I don't know what these "real, practical problems are", but they're certainly not at the syntactic/interface level.

Re: Why asynchronous Rust doesn't work

#455
post #367

Earlier quoted context omitted.

There is a reason why people are moving away from shared memory parallelism. It's not fun to deal with NUMA domains efficiently. For hardware engineers, it's not fun implementing cache coherency protocol efficiently. There is a reason why even Intel was experimenting with channels back in 2010 and you see more and more "network on chip", "cluster on chip" designs with non cache coherent memory. As we need more cores,…

Yes, it's hard, but it's the only way forward.

I don't think you read my comment properly. I'm saying that shared memory parallelism has done its time even at the hardware level within a CPU.

Most programming languages are using construct that assume memory coherency for synchronization (like atomics). It may very well be that hardware channels and DMA become more prevalent in the future as more and more cores are packed and shared memory becomes prohibitive. This would be a totally different paradigm.

Re: Why asynchronous Rust doesn't work

#456
post #356
post #260

Earlier quoted context omitted.

Is this not also true of Rust? Are you saying Rust in some sense hardcodes an implementation to await in a way C++ doesn't? (I am not a Rust programmer, but I am very very curious about this and would appreciate any insight; I do program in C++ with co_await daily, with my own promise/task classes.)

Rust's async/await support is not intended as a general replacement of coroutines. In fact, async/await is built on top of coroutines (what Rust calls "generators"), but these are not yet stable. https://github.com/rust-lang/rust/issues/43122

Ouch... thanks; I didn't realize the Rust situation was this bad :(. FWIW, I do not look at generators as being what I would want as my interface for working with coroutines, and am very much on board there with the comments from tommythorn. I guess I just have too many decades of experience working with coroutines in various systems I have used :(.

https://github.com/rust-lang/rust/issues/43122#issuecomment-...

https://github.com/rust-lang/rust/issues/43122#issuecomment-...

Re: Why asynchronous Rust doesn't work

#457
post #367

Earlier quoted context omitted.

Yes, it's hard, but it's the only way forward.

I don't think you read my comment properly. I'm saying that shared memory parallelism has done its time even at the hardware level within a CPU. Most programming languages are using construct that assume memory coherency for synchronization (like atomics). It may very well be that hardware channels and DMA become more prevalent in the future as more and more cores are packed and shared memory becomes prohibitive. Thi…

Well I don't know the details, but if you read my comment below you'll see that there might be a good reason to use a complex memory model VM with GC.

I can't explain it, just claim that my code works and performs accordingly: https://github.com/tinspin/rupy

Re: Why asynchronous Rust doesn't work

#458
post #236

Earlier quoted context omitted.

Thanks. > a closure which is generally easier to use I didn't experimented much with closures so I can't discuss about simplicity yet (but the article explains that "Closures can be pretty radioactive" ). > within the limitations of C Threads are exposed like that by the operating system. The C implementation is just a way to have access to what the operating system exposes, so I think it should be more like a limita…

It's more the limitations of C: C doesn't have the concept of a closure, so if you are passing around a function with its state, you must pass them around separately (and generally the state must be behind a void pointer, because you can't have a generic interface). In rust and C++ you can make a function with associated state easily and pass it around, both as a raw value with a unique type (though it's difficult to…

Thanks. I'm familiar with all these concepts except for Rust closures and its relationship with multithreading.

I was talking about that at the lowest (OS) level, the thread mechanics is exactly what C presents us as a thread (and all we can do with a thread). The rest (the alternative options C++ and Rust implement) is handy to have, built on top of the standard implementation, but not necessary to actually operate with threads (with tradeoffs, of course). So I don't see it as a limitation: it's just the way it is; the way the OS expose multithreading to its users through its libraries.

Not having those handy features is not a limitation (in what multithread regards). What a I could probably see as a limitation for a system language perhaps it's the fact that I cannot easily reach what the OS exposes (as multithreading implementation) and that I have to use a special construct to reach it. But if in Rust, Closures are just function pointers, then I guess it's fine (and this is what I am trying to understand with your explanations and with the article).

Re: Why asynchronous Rust doesn't work

#459

Earlier quoted context omitted.

If for some magic reason thread::sleep_ms(1000) takes longer than 2000ms the main function would reach its end and deallocate the closure that is about to get called. Basically use after free.

And that's possible, because the sleep call is a suggestion, not a guarantee. If the CPU is blocked for 2s, then the order of waking the threads is undetermined, and the race will occur.

[deleted]

Re: Why asynchronous Rust doesn't work

#460
post #412

Earlier quoted context omitted.

> I am not sure whether this is actually viable. Having investigated this myself, I would be very surprised to discover that it is. The only viable solution to make AsyncRead zero cost for io-uring would be to have required futures to be polled to completion before they are dropped. So you can give up on select and most necessary concurrency primitives. You really want to be able to stop running futures you don't nee…

Well, you can still have select; it "just" has to react to one of the futures becoming ready by cancelling all the other ones and waiting (asynchronously) for the cancellation to be complete. Future doesn't currently have a "cancel" method, but I guess it would just be represented as async drop. So this requires some way of enforcing that async drop is called, which is hard, but I believe it's equally hard as enforci…

> But then, in a typical use of select, you don't actually want to cancel the I/O operations represented by the other futures. Rather, you're running select in a loop in order to handle each completed operation as it comes.

You often do want to cancel them in some branches of the code that handles the result (for example, if they error). It indeed may be prohibitively expensive to wait until cancellation is complete - because io-uring cancellation requires a full round trip through the interface, the IORING_OP_ASYNC_CANCEL op is just a hint to the kernel to cancel any blocking work, you still have to wait to get a completion back before you know the kernel will not touch the buffer passed in.

And this doesn't even get into the much better buffer management strategies io-uring has baked into it, like registered buffers and buffer pre-allocation. I'm really skeptical of making those work with AsyncRead (now you need to define buffer types that deref to slices that are tracking these things independent of the IO object), but since AsyncBufRead lets the IO object own the buffer, it is trivial.

Moving the ecosystem that cares about io-uring to AsyncBufRead (a trait that already exists) and letting the low level IO code handle the buffer is a strictly better solution than requiring futures to run until they're fully, truly cancalled. Protocol libraries should already expose the ability to parse the protocol from an arbitrary stream of buffers, instead of directly owning an IO handle. I'm sure some libraries don't, but that's a mistake that this will course correct.

Post reply on HN