Skip to content

Conversation

@carlosmn
Copy link
Contributor

@carlosmn carlosmn commented Jan 1, 2026

This goes some of the way towards taking care of #51 though right now just for linux-native as it's the easiest to do (at least for me).

Doing async through traits can be a bit challenging. I played around with methods returning boxed futures but then you start having to care about the lifetimes of the buffers and those go into the signatures and it gets ugly.

Which is why I went with how everyone else does it, which is to have a poll_{read,write} method. We then use futures::poll_fn to make a future out of that.

As far as choosing which runtime flavour to use (given you can't go fully generic without implementing a bunch more things that the runtimes give you), I ended up going for both major ones as it was pretty easy to implement them both. The tokio variant works if you're using tokio without extra dependencies, and the smol/async-io version works wherever.

Unfortunately this is quite an explosion of feature flags to enable the user of the library to say what stack they're working on, but I don't know of a way to auto-select depending on what else is selected in a dependent project. And I haven't even tackled what happens if you want async but also basic-udev.

One thing I'm not sure here is the naming of the generic async feature as you're not meant to enable it yourself, but rather choose a backend. Maybe it should be __async to indicate it's internal. This is e.g. what I've seen reqwest does.

This gives us the feature and adds the poll methods required to the trait that
backends need to implement.

It also adds a compile error if you try to enable `async` outside of
`linux-native` which will be the only backend to have it initially.

The `async` feature might need to become more internal depending on how the
implementation goes and how far we can stay generic.
This is a fairly generic way of implementing it with help from the smol
runtime's libraries. The crate will start up a thread in the background to keep
track of the file descriptors that should wake up tasks.

You could go more minimal by implementing the background thread ourselves, but
this code is already tested and cross-platform so we could make use of it for
different OSs in the future.

This changes the relevant feature to `linux-native-async` so the user selects
explicitly which runtime they'd rather use rather than selecting a separate
`async` feature. That feature sticks around as the one that selects the generic
code.
This should help with the code organisation when dealing with different
implementations of the async methods.
This adds the `linux-native-tokio` feature flag to select the tokio runtime.
It adds tokio as a dev dependency on Linux (the only async platform right now)
but that shouldn't affect the users of the library.

The existing co2mon example is modified a bit by making a few things public so
we can re-use that code instead of reimplementing this code which is not even
the point of the library.
While here we can incorporate basic-udev into the matrix.
This is an internal feature so let's give it a name to indicate this.
Instead of making the user specify the backend and the async implementation all
together, let's split them up so they're different features. Right now we only
support one backend, but as we add more, it's going to become more unwieldy.

This already lets us select `linux-native` or `linux-native-basic-udev` and the
async backend, which would have otherwise required its own trio of feature flags.
@carlosmn carlosmn mentioned this pull request Jan 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant