#async-io #future #traits #poll #self #fn #associated

associated-async-io

Async IO traits that use futures instead of poll

2 stable releases

1.0.1 Jun 28, 2019

#1824 in Asynchronous

MIT/Apache

12KB

associate-async-io

crates.io version build status downloads docs.rs docs

Async IO traits that use futures instead of poll. This is an experiment to see if using async fn for the futures::io traits is possible.

Why does this exist?

This is useful because currently implementing AsyncRead, AsyncWrite, and Stream require knowledge of Poll, Pin, and arbitrary self types. We think it would be an ergonomics improvement if knowledge of these concepts was not required to implement these traits. Instead once async fn in traits comes around we think that would make a great fit:

pub trait AsyncRead {
    async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
}

pub trait AsyncWrite {
    async fn write(&mut self, buf: &[u8]) -> io::Result<usize>;
}

pub trait AsyncIterator {
    type Item;
    async fn next(&mut self) -> Option<Self::Item>;
}

These would be direct async counterparts to Read, Write and Iterator:

pub trait AsyncRead {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
}

pub trait AsyncWrite {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize>;
}

pub trait AsyncIterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

However currently async fn in traits doesn't work. So we're defining these traits with an associated type instead.

pub trait AsyncRead {
    type Fut: Future<Output = io::Result<usize>>;
    fn read(&mut self, buf: &mut [u8]) -> Self::Fut;
}

pub trait AsyncWrite {
    type Fut: Future<Output = io::Result<usize>>;
    fn write(&mut self, buf: &[u8]) -> Self::Fut;
}

pub trait AsyncIterator {
    type Item;
    type Fut: Future<Output = Option<Self::Item>>;
    fn next(&mut self) -> Self::Fut;
}

Because of compiler reasons this means there currently is the overhead of an extra box. But we think that's fine, as it's unlikely to become a bottleneck, and this would be temporary anyway.

However a limitation is that this can't return borrowed values, as it relies on GATs. Which seems like the most convincing counterpoint to using these traits today.

Examples

#![feature(async_await)]

use futures::executor::block_on;
use associated_async_io::AsyncIterator;
use futures::future::{self, Future};
use std::pin::Pin;

#[derive(Debug)]
struct KittenIterator {
    cursor: usize,
    kittens: Vec<String>,
}

impl KittenIterator {
    fn new(mut kittens: Vec<String>) -> Self {
        kittens.reverse();
        Self { cursor: 0, kittens }
    }
}

impl AsyncIterator for KittenIterator {
    type Item = String;
    type Fut = Pin<Box<Future<Output = Option<Self::Item>>>>;
    fn next(&mut self) -> Self::Fut {
        self.cursor += 1;
        let kitten = self.kittens.pop();
        Box::pin(future::ready(kitten))
    }
}

fn main () {
    block_on(async {
        let kittens = vec!["chashu".to_owned(), "nori".to_owned()];
        let mut kittens = KittenIterator::new(kittens);
        AsyncIterator::next(&mut kittens);
    })
}

Installation

$ cargo add associate-async-io

Safety

This crate uses #![deny(unsafe_code)] to ensure everything is implemented in 100% Safe Rust.

Contributing

Want to join us? Check out our "Contributing" guide and take a look at some of these issues:

References

None.

License

MIT OR Apache-2.0

No runtime deps