This week's discussions have convinced me to take another look at how libtock-rs could integrate with the futures ecosystem. Unfortunately, I'm having a hard time seeing how that could work. I decided to open this issue to gather ideas.
For example: what should the async equivalent of this API look like?
/// Reads data from the UART into `buffer`. Blocks
/// until the buffer is completely full, then returns.
fn read(buffer: &mut [u8]) { ... }
One idea is to return a type that implements Future which completes when the read is finished:
/// Fills `buffer` with data from the UART. The returned
/// future completes when `buffer` has been filled.
fn read(buffer: &mut [u8]) -> impl Future<Output = ()> { ... }
However, I don't see a sound way to implement that API. If read() calls ReadWriteAllow, then the following invocation causes a use-after-free:
{
let mut buffer = [0; 8];
// Shares buffer with the kernel
let future = read(&mut buffer);
// Prevents the un-allow from happening
core::mem::forget(future);
}
// The kernel has a dangling ReadWriteAllow here.
We could delay sharing buffer with the kernel until the returned Future is polled, but this is unsound as well:
{
let waker = /* Waker creation omitted because it's verbose */;
let mut buffer = [0; 8];
let mut future = Box::pin(read(&mut buffer));
// Shares buffer with the kernel.
let _ = future.as_mut().poll(&mut Context::from_waker(&waker));
// Prevents the un-allow from happening.
core::mem::forget(future);
}
// The kernel has a dangling ReadWriteAllow here.
I can think of other designs that are probably sound, but impractical and/or unergonomic:
// Note the 'static -- this only works with global buffers! IIRC that's
// unacceptable for Ti50, and very expensive in general.
fn read(buffer: &'static mut [u8]) -> impl Future<Output = &'static mut [u8]> { ... }
// Pass ownership of the buffer, relies on dynamic memory allocation. Probably
// more practical than the above but still expensive.
fn read(buffer: Box<[u8]>) -> impl Future<Output = Box<[u8]>> { ... }
Any other ideas on how this could work?
This week's discussions have convinced me to take another look at how
libtock-rscould integrate with the futures ecosystem. Unfortunately, I'm having a hard time seeing how that could work. I decided to open this issue to gather ideas.For example: what should the async equivalent of this API look like?
One idea is to return a type that implements
Futurewhich completes when the read is finished:However, I don't see a sound way to implement that API. If
read()calls ReadWriteAllow, then the following invocation causes a use-after-free:We could delay sharing
bufferwith the kernel until the returnedFutureis polled, but this is unsound as well:I can think of other designs that are probably sound, but impractical and/or unergonomic:
Any other ideas on how this could work?