Hacker News new | ask | show | jobs
by scudd 1914 days ago
Is there any thought to including a default executor in the standard library? I think it's kind of an obstacle for beginners when the language/stdlib provide all the tools to write async code, but not to run it.

I saw discussed in this talk the intent in allowing developers to provide their own executor based on the specifics of their use case: https://youtu.be/NNwK5ZPAJCk?t=1107

This makes sense to me; however, I feel like the defacto at this point is that most people are using tokio. Is there a possibility that a default executor could be provided, and allow developers to override it with a custom library should they chose?

5 comments

I know lots of Rust newcomers have an expectation that the standard library should be all they need, and dependencies should be avoided, but that's not Rust. In Rust dependencies are good, you're supposed to use them. If something can be implemented well outside of std, it should probably remain outside std.

The problem is that a standard library is a heavy burden for a language, and a huge risk for its longevity (think 40 years from now). It promises that the first stable release of any feature will work forever, and never change. It's an unrealistic promise for anything non-trivial.

In other languages it often played out like this:

1. std added a feature,

2. it turned out that it wasn't the best API, but it couldn't be fixed,

3. people fed up with the poor std API wrote a replacement,

4. everyone has to be reminded "don't use the std version, use the replacement instead" forever.

Rust jumps straight to the point 4.

I would worry this would end up like Python, where there is a default async executor in the standard library (asyncio) which was previously developed outside of the standard library (Tulip), but a) it's changed a fair bit between versions, even recently, as they figured out better ways to do things and b) there's been significant work on differently-structured executors (Curio and Trio, notably) as third-party libraries. As an end user of Python, I want to basically entirely use Trio, but there's a fair amount of async Python out there that's been written to assume asyncio.

(There is, at this point, an abstraction library called "anyio" which can run on either an asyncio or Trio event loop, but anyio's model is most strongly influenced by Trio's, because Trio's model is more structured - which means anyio couldn't have been written before Trio was developed and well-received.)

Given that Python has a strong "batteries included in the standard library" approach and Rust has a strong "we shipped a really good package manager with the language, and even the C 'int' type lives outside the standard library, in a package maintained by the Rust core team" approach, it seems like it would be very weird for Rust to repeat the mistake (at least in Rust's worldview) of shipping a default executor in the standard library that would turn into a de facto standard.

And given that Rust does have a really good package manager, adding a third-party dependency is pretty straightforward and doesn't seem like too much of an obstacle. (Frankly, the same is also true of Python, and I would certainly tell a beginner to make a virtualenv and pip install Trio. But since it didn't have it since day one, there's more of a cultural expectation to make the standard library useful out of the box.)

I really really hope this never ever happens. Tokio is great and all, but enshrining it as the default would have a stifling effect and impose a lot of design decisions all over the place.

I do hope that it becomes more possible to write async code that is portable between executors. If there were more standard traits around the most common elements of an executor it'd make things a lot better imo.

It wasn't suggested that it be Tokio, it was suggested that it be a minimal one, that could get you started without needing to make big choices before you even begin. (And yes, portability would have to be a part of that story.)
I took the post mentioning the de-facto normalization of tokio as implying that the default could be tokio, or at least based on tokio. Maybe I misinterpreted, but that was my reading of the post I was replying to.

That said, I think no matter how minimal it is it would still be a mistake. If anything, I think the only thing that would make sense to include is basically a "not-really-async" executor to appease things like the sibling thread where people want to be able to incorporate async code into their sync codebase without spawning a reactor thread. But that would also require the ability to genericize libraries over executors (and would obviously come with some significant caveats around potential deadlocks).

It was suggested in a PR, which was then punted into RFC territory. I’m not aware of anyone actively pursuing an RFC at this time.
Correctly if I'm wrong, but IIRC low-level futures are very dependent on executor? I think you actually told me that, but that was back in futures 0.1 era.
You're probably thinking of IO streams that ultimately have their wakers woken via epoll - they do this by expecting there to be some IO reactor running the epoll loop that they can register their fd+waker with. They don't directly depend on a specific executor, just on the presence of a specific IO reactor. For something specific like tokio's IO streams, it would require tokio to provide its IO reactor as a standalone thing that could be run independent of using its `Runtime` executor.
“Leaf” futures often are, yes. This term (coming from like, a tree) is a tad more descriptive than “low level” IMHO. We have yet to achieve a fully agnostic solution everywhere. That’s part of what work in this area is trying to figure out, as I understand it.
> That’s part of what work in this area is trying to figure out, as I understand it.

Yes, there have been a couple of ideas floating around, and a couple of older RFCs that needed more revision, such as boat's `#[global_executor]`, and discussion about an extension to task::Context that would allow futures to interact with their environment (timers, etc). There is a lot of work going on in this area, but nothing concrete yet.

> Is there any thought to including a default executor in the standard library?

This is more difficult than you think, and you probably don't want the current async stuff to anchor to a weak implementation.

For example, at least one of the current Rust async things doesn't work when pointed to UNIX/Linux character device file descriptors.

I hope not. Then it would be hard to turn async off.

My general position on this is that all-async (like Javascript) is OK, and all-threaded is OK, and all green threads (like Go's goroutines) are OK. But those concepts do not play well together in the same program.

GHC is ok at mixing OS threads and green threads. You launch an OS thread if you want it to make blocking system calls etc. But from the user perspective it's just like a "normal" (green) thread, uses the same mutexes etc.
Are you talking about mio? If so, that might be the bug I've been trying to find all week.