-
Notifications
You must be signed in to change notification settings - Fork 200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Use core language async primitives instead external crates #172
Comments
There are semantics attached to
Having functions that return |
Sure, I just want to make sure this isn't missed. I have done a lot of work on async and have experimented with using it on embedded devices in https://github.com/Nemo157/embrio-rs/ including playing around with alternative trait definitions, just want to offer whatever insights I might have (I am lacking in experience with using the existing embedded ecosystem much, I was only really focused on trying it out with async). If there's a plan for breaking changes to the trait APIs it'd be best to do them all at once, which may involve adding an additional use core::{pin::Pin, task::{Poll, Context}, future::Future};
/// A serial interface
pub trait Serial {
/// Error type associated to this serial interface
type Error;
/// Reads a single byte
fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<u8, Self::Error>>;
/// Writes a single byte
fn poll_write(self: Pin<&mut Self>, byte: u8, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
}
pub trait SerialExt: Serial {
fn read(&mut self) -> impl Future<Output = Result<u8, Self::Error>>;
fn write(&mut self, byte: u8) -> impl Future<Output = Result<u8, Self::Error>>;
} |
It looks as real solution for me. I talk about current PR with @Disasm, he says: we can create clear path for migrate from old |
So the main problem I ran into when trying to do something similar in #149 was that as futures in rust are driven, the returned One can work around this by moving the peripheral itself into the future and only returning it on completion, but this makes for an incredibly obtuse API that makes simple constructs such as loops hard. It is this precise issue with ergonomics that was one of the motivators behind adding native async support to Rust in the first place - see here. It may be that someone smarter than myself can find solutions to these problems, but thought I'd clarify them here. Depending on the timelines for generalized associated types and generator resume arguments, it might be worth waiting for them to to bring native async support to traits and |
The returned One of the nice things about the split between low-level traits of (Generator resume arguments will not affect any public API related to |
@Nemo157 Do you have an example for a |
@Nemo157 You are completely correct, I had a brain fart and conflated
So is the idea that driver crates would then provide methods returning futures? How would people compose and run these without the afformentioned Edit: I believe you would still need the macros |
There is https://docs.rs/embedded-executor/0.3.1/embedded_executor/, I haven't used it myself, but it was developed as part of https://gitlab.com/polymer-kb/polymer.
I think moving to |
@Nemo157 So I'm a little bit confused as to what you are proposing exactly, as far as I can see it these are the following options
And I would argue moving to |
I got a working proof of concept of async/await on a stm32f429 mcu using #172 (comment) approach.
Being not too knowledgeable about RFC process, should I share? |
Yes, please! |
Some remarks: |
I fully support here what @Nemo157 already mentioned. If APIs return Regarding borrowing a peripheral: As @Nemo157 already mentioned, the However for things like GPIO or UART you can also solve the multiple pub trait Serial {
/// Reads a single byte
fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<u8, Self::Error>>;
} where pub trait Serial {
// Obtain a reference to the associated readiness event
fn read_readiness_event(&self) -> &ManualResetEvent;
/// Try to read bytes
fn try_read(&self, mut buffer: &[u8]) -> Poll<usize>;
} In that case the peripheral could be used along: while !done {
serial.read_readiness_event().wait().await;
let received_bytes = serial.try_read(&buffer);
} Multiple tasks can run that coode in parallel. Whether that's desirable or not is certainly a different question. But I think that one is best answered for each peripheral independently. |
Hey all, I just wanted to mention that I have been experimenting with this model here: https://github.com/dflemstr/embedded-platform/ It contains async traits for most of the things in I think we could just copy-paste from that library, I took care to make the traits very re-usable. I also created https://github.com/dflemstr/direct-executor which is an executor that can run without |
hey folks, this (specifically _whether to switch from Related work: |
I've triggered an analogous discussion at rust-embedded-community/embedded-nal#47 and would like to update this discussion based on it (some was implied in posts but it may help to summarize):
(I'm not touching on the topics of how to do things through traits or GAT here; that's described well in the last post.) |
Before we commit on a road that works around GATs (and can not be trivially upgraded to use GATs; not sure whether that's the case with the current proposals), we may want to talk to wg-traits as there's been recent chat that they are making good progress [edit: around rust-lang/rust#44265 (comment)]. |
I've been messing around with this and GATs are pretty much fully usable for returning futures from trait methods at this point. All the ICEs seem to be worked out. |
Now that we have a clear structure for the execution models ( To my knowledge what comes closest to this state is embassy-traits. However, from what I see, embassy is still under heavy development. |
Yes, this is allowed by the Futures trait, although I'd argue that'd be a quite low-quality implementation. Unusable for low-power applications. It'd work okay, it's even "fair" as in all the other tasks get a turn to run before the self-waking task runs again in a multi-task executor. Embassy does hook up the interrupts to wakers, although this means it needs a whole new infrastructure for drivers to "own" interrupts that hasn't been needed in current HALs so far.
Embassy mitigates this by having a custom WakerRegistration type to store wakers. It has 2 impls, an embassy-only one and an executor-agnostic one (enabled by a Cargo feature). The executor-agnostic one stores the waker, the embassy-only one assumes all wakers are embassy wakers and only stores the task pointer (1 word instead of 2) and does direct calls (no vtable). For users that want to use busyloop pseudo-executors there could even be a third "nop" implementation that stores nothing and does nothing on wake. |
This can be closed now that |
Summary
This proposal proposes to change the way how
async api
does implemented. It makesasync api
more clearly for use and extend.Motivation
Our current approach to implement trait primitives as depends on
nb
crate. It was actually on start of this project but after version1.36
rust already haveasync traits
and methodology for work with it. So in 2018 edition of rust lang we have language words asasync
,await
. The marcosses exported bynb
are conflicted with key words, we need to user#
prefix for each macros use.This RFC attempts to change implementation to
core types
based implementation. It will be more effective and more conform rust language ideas.Detailed design
This RFC inspired of PR #13, but after apply external crate I redesign for use core functionality because it will be better than external depends.
For example we have interface for serial:
It use
core::task::Poll
as point ofasync
run status. It was easy for implement and it isn't depends onnb
or another external crates.Alternatives
Don't implement this RFC.
Uresolved questions:
async
code.nb
prefer than rewriteasync
api ofembedded-hal
?The text was updated successfully, but these errors were encountered: