add experimental async/await support. (#582)

This patch adds experimental async/await support to Tokio. It does this
by adding feature flags to existing libs only where necessary in order
to add nightly specific code (mostly `Unpin` implementations). It then
provides a new crate: `tokio-async-await` which is a shim layer on top
of `tokio`.

The `tokio-async-await` crate is expected to look exactly like `tokio`
does, but with async / await support. This strategy reduces the amount
of cfg guarding in the main libraries.

This patch also adds `tokio-channel`, which is copied from futures-rs
0.1 and adds the necessary `Unpin` implementations. In general, futures
0.1 is mostly unmaintained, so it will make sense for Tokio to take over
maintainership of key components regardless of async / await support.
This commit is contained in:
Carl Lerche
2018-08-27 12:24:51 -07:00
committed by GitHub
parent 6e45e0ac61
commit b479ce78d3
42 changed files with 3736 additions and 3 deletions
@@ -0,0 +1,14 @@
/// Wait for a future to complete.
#[macro_export]
macro_rules! await {
($e:expr) => {{
use $crate::std_await;
use $crate::async_await::compat::forward::IntoAwaitable as IntoAwaitableForward;
use $crate::async_await::compat::backward::IntoAwaitable as IntoAwaitableBackward;
#[allow(unused_mut)]
let mut e = $e;
let e = e.into_awaitable();
std_await!(e)
}}
}
@@ -0,0 +1,112 @@
use futures::{
Future as Future01,
Poll as Poll01,
};
use futures_core::{Future as Future03};
use std::boxed::PinBox;
use std::future::FutureObj;
use std::ptr::NonNull;
use std::task::{
Context,
Spawn,
UnsafeWake,
LocalWaker,
Poll as Poll03,
Waker,
SpawnObjError,
};
/// Convert an 0.3 `Future` to an 0.1 `Future`.
#[derive(Debug)]
pub struct Compat<T>(PinBox<T>);
impl<T> Compat<T> {
pub fn new(data: T) -> Compat<T> {
Compat(PinBox::new(data))
}
}
/// Convert a valuee into one that can be used with `await!`.
pub trait IntoAwaitable {
type Awaitable;
fn into_awaitable(self) -> Self::Awaitable;
}
impl<T> IntoAwaitable for T
where T: Future03,
{
type Awaitable = Self;
fn into_awaitable(self) -> Self {
self
}
}
impl<T, Item, Error> Future01 for Compat<T>
where T: Future03<Output = Result<Item, Error>>,
{
type Item = Item;
type Error = Error;
fn poll(&mut self) -> Poll01<Item, Error> {
use futures::Async::*;
let local_waker = noop_local_waker();
let mut executor = NoopExecutor;
let mut cx = Context::new(&local_waker, &mut executor);
let res = self.0.as_pin_mut().poll(&mut cx);
match res {
Poll03::Ready(Ok(val)) => Ok(Ready(val)),
Poll03::Ready(Err(err)) => Err(err),
Poll03::Pending => Ok(NotReady),
}
}
}
// ===== NoopWaker =====
struct NoopWaker;
fn noop_local_waker() -> LocalWaker {
let w: NonNull<NoopWaker> = NonNull::dangling();
unsafe { LocalWaker::new(w) }
}
fn noop_waker() -> Waker {
let w: NonNull<NoopWaker> = NonNull::dangling();
unsafe { Waker::new(w) }
}
unsafe impl UnsafeWake for NoopWaker {
unsafe fn clone_raw(&self) -> Waker {
noop_waker()
}
unsafe fn drop_raw(&self) {
}
unsafe fn wake(&self) {
panic!("NoopWake cannot wake");
}
}
// ===== NoopExecutor =====
struct NoopExecutor;
impl Spawn for NoopExecutor {
fn spawn_obj(&mut self, future: FutureObj<'static, ()>) -> Result<(), SpawnObjError> {
use std::task::SpawnErrorKind;
// NoopExecutor cannot execute
Err(SpawnObjError {
kind: SpawnErrorKind::shutdown(),
future,
})
}
}
@@ -0,0 +1,69 @@
use futures::{Future, Async};
use futures_core::future::Future as Future03;
use futures_core::task::Poll as Poll03;
use std::marker::Unpin;
use std::mem::PinMut;
use std::task::Context;
/// Converts an 0.1 `Future` into an 0.3 `Future`.
#[derive(Debug)]
pub struct Compat<T>(T);
pub(crate) fn convert_poll<T, E>(poll: Result<Async<T>, E>) -> Poll03<Result<T, E>> {
use futures::Async::{Ready, NotReady};
match poll {
Ok(Ready(val)) => Poll03::Ready(Ok(val)),
Ok(NotReady) => Poll03::Pending,
Err(err) => Poll03::Ready(Err(err)),
}
}
pub(crate) fn convert_poll_stream<T, E>(
poll: Result<Async<Option<T>>, E>) -> Poll03<Option<Result<T, E>>>
{
use futures::Async::{Ready, NotReady};
match poll {
Ok(Ready(Some(val))) => Poll03::Ready(Some(Ok(val))),
Ok(Ready(None)) => Poll03::Ready(None),
Ok(NotReady) => Poll03::Pending,
Err(err) => Poll03::Ready(Some(Err(err))),
}
}
/// Convert a value into one that can be used with `await!`.
pub trait IntoAwaitable {
type Awaitable;
/// Convert `self` into a value that can be used with `await!`.
fn into_awaitable(self) -> Self::Awaitable;
}
impl<T: Future + Unpin> IntoAwaitable for T {
type Awaitable = Compat<T>;
fn into_awaitable(self) -> Self::Awaitable {
Compat(self)
}
}
impl<T> Future03 for Compat<T>
where T: Future + Unpin
{
type Output = Result<T::Item, T::Error>;
fn poll(self: PinMut<Self>, _cx: &mut Context) -> Poll03<Self::Output> {
use futures::Async::{Ready, NotReady};
// TODO: wire in cx
match PinMut::get_mut(self).0.poll() {
Ok(Ready(val)) => Poll03::Ready(Ok(val)),
Ok(NotReady) => Poll03::Pending,
Err(e) => Poll03::Ready(Err(e)),
}
}
}
@@ -0,0 +1,4 @@
#![doc(hidden)]
pub mod forward;
pub mod backward;
@@ -0,0 +1,32 @@
use tokio_io::AsyncWrite;
use futures_core::future::Future;
use futures_core::task::{self, Poll};
use std::io;
use std::marker::Unpin;
use std::mem::PinMut;
/// A future used to fully flush an I/O object.
#[derive(Debug)]
pub struct Flush<'a, T: ?Sized + 'a> {
writer: &'a mut T,
}
// PinMut is never projected to fields
impl<'a, T: ?Sized> Unpin for Flush<'a, T> {}
impl<'a, T: AsyncWrite + ?Sized> Flush<'a, T> {
pub(super) fn new(writer: &'a mut T) -> Flush<'a, T> {
Flush { writer }
}
}
impl<'a, T: AsyncWrite + ?Sized> Future for Flush<'a, T> {
type Output = io::Result<()>;
fn poll(mut self: PinMut<Self>, _cx: &mut task::Context) -> Poll<Self::Output> {
use crate::async_await::compat::forward::convert_poll;
convert_poll(self.writer.poll_flush())
}
}
+192
View File
@@ -0,0 +1,192 @@
//! Use I/O with `async` / `await`.
mod flush;
mod read;
mod read_exact;
mod write;
mod write_all;
pub use self::flush::Flush;
pub use self::read::Read;
pub use self::read_exact::ReadExact;
pub use self::write::Write;
pub use self::write_all::WriteAll;
use tokio_io::{AsyncRead, AsyncWrite};
/// An extension trait which adds utility methods to `AsyncRead` types.
pub trait AsyncReadExt: AsyncRead {
/// Tries to read some bytes directly into the given `buf` in asynchronous
/// manner, returning a future.
///
/// The returned future will resolve to the number of bytes read once the read
/// operation is completed.
///
/// # Examples
///
/// ```
/// #![feature(async_await, await_macro, futures_api)]
/// tokio::run_async(async {
/// // The extension trait can also be imported with
/// // `use tokio::prelude::*`.
/// use tokio::prelude::AsyncReadExt;
/// use std::io::Cursor;
///
/// let mut reader = Cursor::new([1, 2, 3, 4]);
/// let mut output = [0u8; 5];
///
/// let bytes = await!(reader.read_async(&mut output[..])).unwrap();
///
/// // This is only guaranteed to be 4 because `&[u8]` is a synchronous
/// // reader. In a real system you could get anywhere from 1 to
/// // `output.len()` bytes in a single read.
/// assert_eq!(bytes, 4);
/// assert_eq!(output, [1, 2, 3, 4, 0]);
/// });
/// ```
fn read_async<'a>(&'a mut self, buf: &'a mut [u8]) -> Read<'a, Self> {
Read::new(self, buf)
}
/// Creates a future which will read exactly enough bytes to fill `buf`,
/// returning an error if end of file (EOF) is hit sooner.
///
/// The returned future will resolve once the read operation is completed.
///
/// In the case of an error the buffer and the object will be discarded, with
/// the error yielded.
///
/// # Examples
///
/// ```
/// #![feature(async_await, await_macro, futures_api)]
/// tokio::run_async(async {
/// // The extension trait can also be imported with
/// // `use tokio::prelude::*`.
/// use tokio::prelude::AsyncReadExt;
/// use std::io::Cursor;
///
/// let mut reader = Cursor::new([1, 2, 3, 4]);
/// let mut output = [0u8; 4];
///
/// await!(reader.read_exact_async(&mut output)).unwrap();
///
/// assert_eq!(output, [1, 2, 3, 4]);
/// });
/// ```
///
/// ## EOF is hit before `buf` is filled
///
/// ```
/// #![feature(async_await, await_macro, futures_api)]
/// tokio::run_async(async {
/// // The extension trait can also be imported with
/// // `use tokio::prelude::*`.
/// use tokio::prelude::AsyncReadExt;
/// use std::io::{self, Cursor};
///
/// let mut reader = Cursor::new([1, 2, 3, 4]);
/// let mut output = [0u8; 5];
///
/// let result = await!(reader.read_exact_async(&mut output));
///
/// assert_eq!(result.unwrap_err().kind(), io::ErrorKind::UnexpectedEof);
/// });
/// ```
fn read_exact_async<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadExact<'a, Self> {
ReadExact::new(self, buf)
}
}
/// An extension trait which adds utility methods to `AsyncWrite` types.
pub trait AsyncWriteExt: AsyncWrite {
/// Write data into this object.
///
/// Creates a future that will write the entire contents of the buffer `buf` into
/// this `AsyncWrite`.
///
/// The returned future will not complete until all the data has been written.
///
/// # Examples
///
/// ```
/// #![feature(async_await, await_macro, futures_api)]
/// tokio::run_async(async {
/// // The extension trait can also be imported with
/// // `use tokio::prelude::*`.
/// use tokio::prelude::AsyncWriteExt;
/// use std::io::Cursor;
///
/// let mut buf = [0u8; 5];
/// let mut writer = Cursor::new(&mut buf[..]);
///
/// let n = await!(writer.write_async(&[1, 2, 3, 4])).unwrap();
///
/// assert_eq!(writer.into_inner()[..n], [1, 2, 3, 4, 0][..n]);
/// });
/// ```
fn write_async<'a>(&'a mut self, buf: &'a [u8]) -> Write<'a, Self> {
Write::new(self, buf)
}
/// Write an entire buffer into this object.
///
/// Creates a future that will write the entire contents of the buffer `buf` into
/// this `AsyncWrite`.
///
/// The returned future will not complete until all the data has been written.
///
/// # Examples
///
/// ```
/// #![feature(async_await, await_macro, futures_api)]
/// tokio::run_async(async {
/// // The extension trait can also be imported with
/// // `use tokio::prelude::*`.
/// use tokio::prelude::AsyncWriteExt;
/// use std::io::Cursor;
///
/// let mut buf = [0u8; 5];
/// let mut writer = Cursor::new(&mut buf[..]);
///
/// await!(writer.write_all_async(&[1, 2, 3, 4])).unwrap();
///
/// assert_eq!(writer.into_inner(), [1, 2, 3, 4, 0]);
/// });
/// ```
fn write_all_async<'a>(&'a mut self, buf: &'a [u8]) -> WriteAll<'a, Self> {
WriteAll::new(self, buf)
}
/// Creates a future which will entirely flush this `AsyncWrite`.
///
/// # Examples
///
/// ```
/// #![feature(async_await, await_macro, futures_api)]
/// tokio::run_async(async {
/// // The extension trait can also be imported with
/// // `use tokio::prelude::*`.
/// use tokio::prelude::AsyncWriteExt;
/// use std::io::{BufWriter, Cursor};
///
/// let mut output = [0u8; 5];
///
/// {
/// let mut writer = Cursor::new(&mut output[..]);
/// let mut buffered = BufWriter::new(writer);
/// await!(buffered.write_all_async(&[1, 2])).unwrap();
/// await!(buffered.write_all_async(&[3, 4])).unwrap();
/// await!(buffered.flush_async()).unwrap();
/// }
///
/// assert_eq!(output, [1, 2, 3, 4, 0]);
/// });
/// ```
fn flush_async<'a>(&mut self) -> Flush<Self> {
Flush::new(self)
}
}
impl<T: AsyncRead + ?Sized> AsyncReadExt for T {}
impl<T: AsyncWrite + ?Sized> AsyncWriteExt for T {}
@@ -0,0 +1,38 @@
use tokio_io::AsyncRead;
use futures_core::future::Future;
use futures_core::task::{self, Poll};
use std::io;
use std::marker::Unpin;
use std::mem::PinMut;
/// A future which can be used to read bytes.
#[derive(Debug)]
pub struct Read<'a, T: ?Sized + 'a> {
reader: &'a mut T,
buf: &'a mut [u8],
}
// Pinning is never projected to fields
impl<'a, T: ?Sized> Unpin for Read<'a, T> {}
impl<'a, T: AsyncRead + ?Sized> Read<'a, T> {
pub(super) fn new(reader: &'a mut T, buf: &'a mut [u8]) -> Read<'a, T> {
Read {
reader,
buf,
}
}
}
impl<'a, T: AsyncRead + ?Sized> Future for Read<'a, T> {
type Output = io::Result<usize>;
fn poll(mut self: PinMut<Self>, _cx: &mut task::Context) -> Poll<Self::Output> {
use crate::async_await::compat::forward::convert_poll;
let this = &mut *self;
convert_poll(this.reader.poll_read(this.buf))
}
}
@@ -0,0 +1,56 @@
use tokio_io::AsyncRead;
use futures_core::future::Future;
use futures_core::task::{self, Poll};
use futures_util::try_ready;
use std::io;
use std::marker::Unpin;
use std::mem::{self, PinMut};
/// A future which can be used to read exactly enough bytes to fill a buffer.
#[derive(Debug)]
pub struct ReadExact<'a, T: ?Sized + 'a> {
reader: &'a mut T,
buf: &'a mut [u8],
}
// Pinning is never projected to fields
impl<'a, T: ?Sized> Unpin for ReadExact<'a, T> {}
impl<'a, T: AsyncRead + ?Sized> ReadExact<'a, T> {
pub(super) fn new(reader: &'a mut T, buf: &'a mut [u8]) -> ReadExact<'a, T> {
ReadExact {
reader,
buf,
}
}
}
fn eof() -> io::Error {
io::Error::new(io::ErrorKind::UnexpectedEof, "early eof")
}
impl<'a, T: AsyncRead + ?Sized> Future for ReadExact<'a, T> {
type Output = io::Result<()>;
fn poll(mut self: PinMut<Self>, _cx: &mut task::Context) -> Poll<Self::Output> {
use crate::async_await::compat::forward::convert_poll;
let this = &mut *self;
while !this.buf.is_empty() {
let n = try_ready!(convert_poll(this.reader.poll_read(this.buf)));
{
let (_, rest) = mem::replace(&mut this.buf, &mut []).split_at_mut(n);
this.buf = rest;
}
if n == 0 {
return Poll::Ready(Err(eof()))
}
}
Poll::Ready(Ok(()))
}
}
@@ -0,0 +1,38 @@
use tokio_io::AsyncWrite;
use futures_core::future::Future;
use futures_core::task::{self, Poll};
use std::io;
use std::marker::Unpin;
use std::mem::PinMut;
/// A future used to write data.
#[derive(Debug)]
pub struct Write<'a, T: 'a + ?Sized> {
writer: &'a mut T,
buf: &'a [u8],
}
// Pinning is never projected to fields
impl<'a, T: ?Sized> Unpin for Write<'a, T> {}
impl<'a, T: AsyncWrite + ?Sized> Write<'a, T> {
pub(super) fn new(writer: &'a mut T, buf: &'a [u8]) -> Write<'a, T> {
Write {
writer,
buf,
}
}
}
impl<'a, T: AsyncWrite + ?Sized> Future for Write<'a, T> {
type Output = io::Result<usize>;
fn poll(mut self: PinMut<Self>, _cx: &mut task::Context) -> Poll<io::Result<usize>> {
use crate::async_await::compat::forward::convert_poll;
let this = &mut *self;
convert_poll(this.writer.poll_write(this.buf))
}
}
@@ -0,0 +1,57 @@
use tokio_io::AsyncWrite;
use futures_core::future::Future;
use futures_core::task::{self, Poll};
use futures_util::try_ready;
use std::io;
use std::marker::Unpin;
use std::mem::{self, PinMut};
/// A future used to write the entire contents of a buffer.
#[derive(Debug)]
pub struct WriteAll<'a, T: ?Sized + 'a> {
writer: &'a mut T,
buf: &'a [u8],
}
// Pinning is never projected to fields
impl<'a, T: ?Sized> Unpin for WriteAll<'a, T> {}
impl<'a, T: AsyncWrite + ?Sized> WriteAll<'a, T> {
pub(super) fn new(writer: &'a mut T, buf: &'a [u8]) -> WriteAll<'a, T> {
WriteAll {
writer,
buf,
}
}
}
fn zero_write() -> io::Error {
io::Error::new(io::ErrorKind::WriteZero, "zero-length write")
}
impl<'a, T: AsyncWrite + ?Sized> Future for WriteAll<'a, T> {
type Output = io::Result<()>;
fn poll(mut self: PinMut<Self>, _cx: &mut task::Context) -> Poll<io::Result<()>> {
use crate::async_await::compat::forward::convert_poll;
let this = &mut *self;
while !this.buf.is_empty() {
let n = try_ready!(convert_poll(this.writer.poll_write(this.buf)));
{
let (_, rest) = mem::replace(&mut this.buf, &[]).split_at(n);
this.buf = rest;
}
if n == 0 {
return Poll::Ready(Err(zero_write()))
}
}
Poll::Ready(Ok(()))
}
}
+8
View File
@@ -0,0 +1,8 @@
//! Utilities for working with `async` / `await`.
#[macro_use]
mod await;
pub mod compat;
pub mod io;
pub mod sink;
pub mod stream;
@@ -0,0 +1,26 @@
//! Use sinks with `async` / `await`.
mod send;
pub use self::send::Send;
use futures::Sink;
use std::marker::Unpin;
/// An extension trait which adds utility methods to `Sink` types.
pub trait SinkExt: Sink {
/// Send an item into the sink.
///
/// Note that, **because of the flushing requirement, it is usually better
/// to batch together items to send via `send_all`, rather than flushing
/// between each item.**
fn send_async(&mut self, item: Self::SinkItem) -> Send<Self>
where
Self: Sized + Unpin,
{
Send::new(self, item)
}
}
impl<T: Sink> SinkExt for T {}
@@ -0,0 +1,59 @@
use futures::Sink;
use futures_core::future::Future;
use futures_core::task::{self, Poll};
use std::marker::Unpin;
use std::mem::PinMut;
/// Future for the `SinkExt::send_async` combinator, which sends a value to a
/// sink and then waits until the sink has fully flushed.
#[derive(Debug)]
pub struct Send<'a, T: Sink + 'a + ?Sized> {
sink: &'a mut T,
item: Option<T::SinkItem>,
}
impl<T: Sink + Unpin + ?Sized> Unpin for Send<'_, T> {}
impl<'a, T: Sink + Unpin + ?Sized> Send<'a, T> {
pub(super) fn new(sink: &'a mut T, item: T::SinkItem) -> Self {
Send {
sink,
item: Some(item),
}
}
}
impl<T: Sink + Unpin + ?Sized> Future for Send<'_, T> {
type Output = Result<(), T::SinkError>;
fn poll(mut self: PinMut<Self>, _cx: &mut task::Context) -> Poll<Self::Output> {
use crate::async_await::compat::forward::convert_poll;
use futures::AsyncSink::{Ready, NotReady};
use futures_util::try_ready;
// use crate::compat::forward::convert_poll;
let this = &mut *self;
if let Some(item) = this.item.take() {
match this.sink.start_send(item) {
Ok(Ready) => {}
Ok(NotReady(val)) => {
self.item = Some(val);
return Poll::Pending;
}
Err(err) => {
return Poll::Ready(Err(err));
}
}
}
// we're done sending the item, but want to block on flushing the
// sink
try_ready!(convert_poll(this.sink.poll_complete()));
Poll::Ready(Ok(()))
}
}
@@ -0,0 +1,40 @@
//! Use streams with `async` / `await`.
mod next;
pub use self::next::Next;
use futures::Stream;
use std::marker::Unpin;
/// An extension trait which adds utility methods to `Stream` types.
pub trait StreamExt: Stream {
/// Creates a future that resolves to the next item in the stream.
///
/// # Examples
///
/// ```
/// #![feature(await_macro, async_await)]
/// tokio::run_async(async {
/// // The extension trait can also be imported with
/// // `use tokio::prelude::*`.
/// use tokio::prelude::{stream, StreamExt};
///
/// let mut stream = stream::iter_ok::<_, ()>(1..3);
///
/// assert_eq!(await!(stream.next()), Some(Ok(1)));
/// assert_eq!(await!(stream.next()), Some(Ok(2)));
/// assert_eq!(await!(stream.next()), Some(Ok(3)));
/// assert_eq!(await!(stream.next()), None);
/// });
/// ```
fn next(&mut self) -> Next<Self>
where
Self: Sized + Unpin,
{
Next::new(self)
}
}
impl<T: Stream> StreamExt for T {}
@@ -0,0 +1,31 @@
use futures::Stream;
use futures_core::future::Future;
use futures_core::task::{self, Poll};
use std::marker::Unpin;
use std::mem::PinMut;
/// A future of the next element of a stream.
#[derive(Debug)]
pub struct Next<'a, T: 'a> {
stream: &'a mut T,
}
impl<'a, T: Stream + Unpin> Unpin for Next<'a, T> {}
impl<'a, T: Stream + Unpin> Next<'a, T> {
pub(super) fn new(stream: &'a mut T) -> Next<'a, T> {
Next { stream }
}
}
impl<'a, T: Stream + Unpin> Future for Next<'a, T> {
type Output = Option<Result<T::Item, T::Error>>;
fn poll(self: PinMut<Self>, _cx: &mut task::Context) -> Poll<Self::Output> {
use crate::async_await::compat::forward::convert_poll_stream;
convert_poll_stream(
PinMut::get_mut(self).stream.poll())
}
}
+102
View File
@@ -0,0 +1,102 @@
#![feature(futures_api, await_macro, pin, arbitrary_self_types)]
#![doc(html_root_url = "https://docs.rs/tokio-async-await/0.1.0")]
#![deny(missing_docs, missing_debug_implementations)]
#![cfg_attr(test, deny(warnings))]
//! A preview of Tokio w/ `async` / `await` support.
extern crate futures;
extern crate futures_core;
extern crate futures_util;
// Re-export all of Tokio
pub use tokio_main::{
// Modules
clock,
codec,
executor,
fs,
io,
net,
reactor,
runtime,
timer,
util,
// Functions
run,
spawn,
};
pub mod sync {
//! Asynchronous aware synchronization
pub use tokio_channel::{
mpsc,
oneshot,
};
}
pub mod async_await;
pub mod prelude {
//! A "prelude" for users of the `tokio` crate.
//!
//! This prelude is similar to the standard library's prelude in that you'll
//! almost always want to import its entire contents, but unlike the standard
//! library's prelude you'll have to do so manually:
//!
//! ```
//! use tokio::prelude::*;
//! ```
//!
//! The prelude may grow over time as additional items see ubiquitous use.
pub use tokio_main::prelude::*;
#[doc(inline)]
pub use crate::async_await::{
io::{
AsyncReadExt,
AsyncWriteExt,
},
sink::{
SinkExt,
},
stream::{
StreamExt,
},
};
}
use futures_core::{
Future as Future03,
};
// Rename the `await` macro in `std`
#[doc(hidden)]
#[macro_export]
pub use std::await as std_await;
/// Like `tokio::run`, but takes an `async` block
pub fn run_async<F>(future: F)
where F: Future03<Output = ()> + Send + 'static,
{
use futures_util::future::FutureExt;
use crate::async_await::compat::backward;
let future = future.map(|_| Ok(()));
run(backward::Compat::new(future))
}
/// Like `tokio::spawn`, but takes an `async` block
pub fn spawn_async<F>(future: F)
where F: Future03<Output = ()> + Send + 'static,
{
use futures_util::future::FutureExt;
use crate::async_await::compat::backward;
let future = future.map(|_| Ok(()));
spawn(backward::Compat::new(future));
}