224: Re-export IO traits from futures r=stjepang a=stjepang Sorry for the big PR! Instead of providing our own traits `async_std::io::{Read, Write, Seek, BufRead}`, we now re-export `futures::io::{AsyncRead, AsyncWrite, AsyncSeek, AsyncRead}`. While re-exporting we rename them to strip away the "Async" prefix. The documentation will display the contents of the original traits from the `futures` crate together with our own extension methods. There's a note in the docs saying the extenion methods become available only when `async_std::prelude::*` is imported. Our extension traits are re-exported into the prelude, but are marked with `#[doc(hidden)]` so they're completely invisible to users. The benefit of this is that people can now implement traits from `async_std::io` for their types and stay compatible with `futures`. This will also simplify some trait bounds in our APIs - for example, things like `where Self: futures_io::AsyncRead`. At the same time, I cleaned up some trait bounds in our stream interfaces, but haven't otherwise fiddled with them much. I intend to follow up with another PR doing the same change for `Stream` so that we re-export the stream trait from `futures`. Co-authored-by: Stjepan Glavina <stjepang@gmail.com>
3.6 KiB
Writing an Accept Loop
Let's implement the scaffold of the server: a loop that binds a TCP socket to an address and starts accepting connections.
First of all, let's add required import boilerplate:
# extern crate async_std;
use async_std::{
prelude::*, // 1
task, // 2
net::{TcpListener, ToSocketAddrs}, // 3
};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; // 4
prelude
re-exports some traits required to work with futures and streams.- The
task
module roughly corresponds to thestd::thread
module, but tasks are much lighter weight. A single thread can run many tasks. - For the socket type, we use
TcpListener
fromasync_std
, which is just likestd::net::TcpListener
, but is non-blocking and usesasync
API. - We will skip implementing comprehensive error handling in this example.
To propagate the errors, we will use a boxed error trait object.
Do you know that there's
From<&'_ str> for Box<dyn Error>
implementation in stdlib, which allows you to use strings with?
operator?
Now we can write the server's accept loop:
# extern crate async_std;
# use async_std::{
# net::{TcpListener, ToSocketAddrs},
# prelude::*,
# };
#
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { // 1
let listener = TcpListener::bind(addr).await?; // 2
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await { // 3
// TODO
}
Ok(())
}
- We mark the
accept_loop
function asasync
, which allows us to use.await
syntax inside. TcpListener::bind
call returns a future, which we.await
to extract theResult
, and then?
to get aTcpListener
. Note how.await
and?
work nicely together. This is exactly howstd::net::TcpListener
works, but with.await
added. Mirroring API ofstd
is an explicit design goal ofasync_std
.- Here, we would like to iterate incoming sockets, just how one would do in
std
:
let listener: std::net::TcpListener = unimplemented!();
for stream in listener.incoming() {
}
Unfortunately this doesn't quite work with async
yet, because there's no support for async
for-loops in the language yet.
For this reason we have to implement the loop manually, by using while let Some(item) = iter.next().await
pattern.
Finally, let's add main:
# extern crate async_std;
# use async_std::{
# net::{TcpListener, ToSocketAddrs},
# prelude::*,
# task,
# };
#
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#
# async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { // 1
# let listener = TcpListener::bind(addr).await?; // 2
# let mut incoming = listener.incoming();
# while let Some(stream) = incoming.next().await { // 3
# // TODO
# }
# Ok(())
# }
#
// main
fn run() -> Result<()> {
let fut = accept_loop("127.0.0.1:8080");
task::block_on(fut)
}
The crucial thing to realise that is in Rust, unlike other languages, calling an async function does not run any code.
Async functions only construct futures, which are inert state machines.
To start stepping through the future state-machine in an async function, you should use .await
.
In a non-async function, a way to execute a future is to hand it to the executor.
In this case, we use task::block_on
to execute a future on the current thread and block until it's done.