mirror of
https://github.com/async-rs/async-std.git
synced 2025-05-26 00:41:32 +00:00
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>
96 lines
3.6 KiB
Markdown
96 lines
3.6 KiB
Markdown
## 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:
|
|
|
|
```rust,edition2018
|
|
# 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
|
|
```
|
|
|
|
1. `prelude` re-exports some traits required to work with futures and streams.
|
|
2. The `task` module roughly corresponds to the `std::thread` module, but tasks are much lighter weight.
|
|
A single thread can run many tasks.
|
|
3. For the socket type, we use `TcpListener` from `async_std`, which is just like `std::net::TcpListener`, but is non-blocking and uses `async` API.
|
|
4. 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:
|
|
|
|
```rust,edition2018
|
|
# 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(())
|
|
}
|
|
```
|
|
|
|
1. We mark the `accept_loop` function as `async`, which allows us to use `.await` syntax inside.
|
|
2. `TcpListener::bind` call returns a future, which we `.await` to extract the `Result`, and then `?` to get a `TcpListener`.
|
|
Note how `.await` and `?` work nicely together.
|
|
This is exactly how `std::net::TcpListener` works, but with `.await` added.
|
|
Mirroring API of `std` is an explicit design goal of `async_std`.
|
|
3. Here, we would like to iterate incoming sockets, just how one would do in `std`:
|
|
|
|
```rust,edition2018,should_panic
|
|
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:
|
|
|
|
```rust,edition2018
|
|
# 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.
|