Crates.io | some_executor |
lib.rs | some_executor |
version | 0.2.0 |
source | src |
created_at | 2024-10-14 04:57:16.99739 |
updated_at | 2024-11-07 21:20:14.909559 |
description | A trait for libraries that abstract over any executor |
homepage | https://sealedabstract.com/code/some_executor |
repository | https://github.com/drewcrawford/some_executor |
max_upload_size | |
id | 1407930 |
size | 103,976 |
Rust made the terrible mistake of not having an async executor in std. And worse: there is no trait for executors to implement, nor a useful API for users to expect. The result is everyone has to write their code to a specific executor, and it's always tokio. But tokio has too many drawbacks to make it the universal choice, and the other executors are too cumbersome to be practical. As a result, async rust is stuck in limbo.
There are many proposals to fix this. This one's mine. Here's how it works:
If you want to execute futures, this crate provides a simple, obvious trait to spawn your future onto "some" executor. The rich APIs are thoughtfully designed to support typical applications, be fast, compatible with many executors, and future-proof. For example, you can take an executor as a generic argument, giving the compiler the opportunity to specialize your code for the specific executor. Or, you can spawn a task onto a global executor via dynamic dispatch. You can provide rich scheduling information that can be used by the executor to prioritize tasks. You can do all this in a modular and futureproof way.
If you want to implement an executor, this crate provides a simple, obvious trait to receive futures and execute them, and plug into the ecosystem. Moreover, advanced features like cancellation are implemented for you, so you get them for free and can focus on the core logic of your executor.
If you want to write async code, this crate provides a standard, robust featureset that (in my opinion) is table-stakes for writing async rust in 2024. This includes cancellation, task locals, priorities, and much more. These features are portable and dependable across any executor.
Here are deeper dives on each topic.
some_executor provides many different API options for many usecases.
SomeExecutorExt
trait provides an interface to spawn onto an executor. You can take it as a generic argument, specializing your code/types against
the executor you want to use.LocalExecutorExt
trait provides the analogous interface for local executors (for your futures which are !Send
).SomeExecutor
and SomeLocalExecutor
, are for when you want to store your executor in a struct by erasing their type. This has the usual tradeoffs around boxing types.current_executor
or thread level thread_executor
. This is useful in case you don't want to take an executor as an argument, but your caller probably has one, and you can borrow that.global_executor
. This is useful in case you don't want to take it as an argument, you aren't sure what your caller is doing (for example you might be handling a signal), and you nonetheless want to spawn a task.Spawning a task is as simple as calling spawn
on any of the executor types. Then you get an Observer
object that you can use to get the results of the task, if interested, or cancel the task.
test_executors
(https://sealedabstract.com/code/test_executors) provides a set of toy executors good enough for unit tests.some_local_executor
(https://sealedabstract.com/code/some_local_executor) provides a local executor that runs its task on the current thread, and can also receive tasks from other threads.Here are your APIs:
SomeExecutorExt
trait. This supports a wide variety of callers and patterns.LocalExecutorExt
trait. This type can spawn futures that are !Send
.ExecutorNotified
trait. This is optional, but can provide some efficiency.The main gotcha of this API is that you must wait to poll tasks until after Task::poll_after
. You can
accomplish this any way you like, such as suspending the task, sleeping, etc. For more details, see the documentation.
Mostly, write the code you want to write. But here are some benefits you can get from this crate:
Task
s from your async code, see above.task_local
macro, which is comparable to thread_local
or tokio's version. It provides a way to store data that is local to the task.task::TASK_ID
and task::TASK_LABEL
, which are useful for debugging and logging information about the current task.task::TASK_PRIORITY
, which can be used to provide useful downstream information about how the task is executing.task::IS_CANCELLED
local, which can be used to check if the task has been cancelled. This allows you to return early and avoid unnecessary work.executor-trait
One way to understand this crate is as an alternative to the executor-trait project. While I like it a lot, here's why I made this instead:
()
.Philosophically, the difference is that executor-trait
ships the lowest-common denominator API that all executors can support. While this
crate ships the highest-common denominator API that all async code can use, together with polyfills and fallbacks so all executors
can use them even if they don't support them natively. The result is rich, fast, easy, and portable async rust.
It is straightforward to implement the API of this crate in terms of executor-trait
, as well as the reverse. So it is possible
to use both projects together.
This interface is unstable and may change.