Skip to content

Instantly share code, notes, and snippets.

@nrc
Created September 7, 2022 12:56
Show Gist options
  • Save nrc/18d5a57b3b37229b4c9c69038ce9eff2 to your computer and use it in GitHub Desktop.
Save nrc/18d5a57b3b37229b4c9c69038ce9eff2 to your computer and use it in GitHub Desktop.
async IO traits
use std::io::Result;
use std::mem::{self, MaybeUninit};
// A simple async-ification of sync Read plus downcasting methods.
pub trait Read {
// async
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
// ...
// TODO should we have dynamic and/or static (Option<&mut impl ReadyRead>) versions?
fn as_ready(&mut self) -> Option<&mut dyn ReadyRead> {
None
}
// TODO OwnedRead is not object-safe. Could either return impl OwnedRead, or maybe dyn* helps?
// Or change the sig of OwnedRead::read.
fn as_owned(&mut self) -> Option<&mut dyn OwnedRead> {
None
}
}
// Used for completion model systems.
pub trait OwnedRead: Read {
// async
fn read<B: OwnedBuf>(&mut self, buf: B) -> (B, Result<()>);
}
// Abstracts over Vec<u8> and similar data structures
pub trait OwnedBuf: Default + 'static {
fn take(&mut self) -> Self {
mem::take(self)
}
/// Returns a shared reference to the filled portion of the buffer.
fn filled(&self) -> &[u8];
unsafe fn unfilled(&mut self) -> &mut [MaybeUninit<u8>];
fn init_mut(&mut self) -> &mut [u8];
fn uninit_mut(&mut self) -> &mut [MaybeUninit<u8>];
unsafe fn advance(&mut self, n: usize);
unsafe fn set_init(&mut self, n: usize);
fn ensure_init(&mut self);
fn append(&mut self, buf: &[u8]);
// TODO does this work?
fn borrow<'a>(&'a mut self) -> BorrowedBuf<'a>;
}
// Used for epoll-like systems
pub trait Ready {
// async
fn ready(&mut self, interest: Interest) -> Result<Readiness>;
}
pub trait ReadyRead: Ready + Read {
fn non_blocking_read(&mut self, buf: &mut [u8]) -> Result<NonBlocking<usize>>;
// ...
}
/// Express which notifications the user is interested in receiving.
#[derive(Copy, Clone)]
pub struct Interest(u32);
/// Describes which operations are ready for an IO resource.
#[derive(Copy, Clone)]
pub struct Readiness(u32);
/// Whether an IO operation is ready for reading/writing or would block.
#[derive(Copy, Clone, Debug)]
pub enum NonBlocking<T> {
Ready(T),
WouldBlock,
}
#[cfg(test)]
mod tests {
use super::*;
// Some example readable resource, provided by a runtime.
struct Socket;
// A 'good citizen' implementation of Socket would support all appropriate traits (usually basic
// Read and one of the specialised traits).
impl Read for Socket {}
impl OwnedRead for Socket {}
impl ReadyRead for Socket {}
fn simple_read() -> Result<()> {
let mut s = Socket;
let mut buf = [0; 1024];
s.read(&mut buf)?;
// Use buf
// See generic versions for owned/ready
}
fn generic_read(reader: &mut impl Read) -> Result<()> {
let mut buf = [0; 1024];
reader.read(&mut buf)?;
// Use buf
}
fn generic_owned_read(reader: &mut impl OwnedRead) -> Result<()> {
let mut buf = vec![0; 1024];
let result = reader.read(buf);
buf = result.0;
result.1?;
// Use buf
}
fn generic_ready_read(reader: &mut impl ReadyRead) -> Result<()> {
loop {
reader.ready(Interest::READABLE)/*.await*/?;
let mut buf = [0; 1024];
if let NonBlocking::Ready(n) = reader.non_blocking_read(buf)? {
// Use buf
return Ok(());
}
}
}
fn advanced_read(reader: &mut impl Read) -> Result<()> {
if let Some(reader) = reader.as_owned() {
let mut buf = vec![0; 1024];
let result = reader.read(buf);
buf = result.0;
result.1?;
// Use buf
} else if let Some(reader) = reader.as_ready() {
loop {
reader.ready(Interest::READABLE)/*.await*/?;
let mut buf = [0; 1024];
if let NonBlocking::Ready(n) = reader.non_blocking_read(buf)? {
// Use buf
return Ok(());
}
}
} else {
let mut buf = [0; 1024];
reader.read(&mut buf)?;
// Use buf
}
}
// An example of a 'middleware' library which should support all runtimes.
struct Buffer<T: Read> {
reader: T,
buf: Vec<u8>,
}
// Good citizen libraries have to support all three traits.
impl Read for Buffer {
// read should use an advanced_read style, since even if the caller does
// not use the trick, the advanced modes might be more efficient.
}
// These two impls have to downcast self.reader and either panic or use a
// slow path if the downcast fails.
impl OwnedRead for Buffer {}
impl ReadyRead for Buffer {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment