I/O Overview

The Rust standard library provides support for networking and I/O, suchas TCP connections, UDP sockets, reading from and writing to files, etc.However, those operations are all synchronous, or blocking, meaningthat when you call them, the current thread may stop executing and go tosleep until it is unblocked. For example, the read method instd:![:io:](https://www.emoji-cheat-sheet.com/graphics/emojis/io.png ":io:"):Read will block until there is data to read. In the worldof futures, that behavior is unfortunate, since we would like tocontinue executing other futures we may have while waiting for the I/Oto complete.

To enable this, Tokio provides non-blocking versions of many standardlibrary I/O resources, such as file operations and TCP, UDP, andUnix sockets. They return futures for long-running operations (likeaccepting a new TCP connection), and implement non-blocking variants ofstd:![:io:](https://www.emoji-cheat-sheet.com/graphics/emojis/io.png ":io:"):Read and std:![:io:](https://www.emoji-cheat-sheet.com/graphics/emojis/io.png ":io:"):Write called AsyncRead andAsyncWrite.

Non-blocking reads and writes do not block if, for example, there is nomore data available. Instead, they return immediately with aWouldBlock error, along with a guarantee (like Future::poll) thatthey have arranged for the current task to be woken up when they canlater make progress, such as when a network packet arrives.

By using the non-blocking Tokio I/O types, a future that performs I/Ono longer blocks execution of other futures if the I/O they wish toperform cannot be performed immediately. Instead, it simply returnsNotReady, and relies on a task notification to cause poll to becalled again, and which point its I/O should succeed without blocking.

Behind the scenes, Tokio uses mio and tokio-fs to keep track ofthe status of the various I/O resources that different futures arewaiting for, and is notified by the operating system whenever the statusof any of them change.

An example server

To get a sense of how this fits together, consider this echoserver implementation:

1. # extern crate tokio;
2. use tokio::prelude::*;
3. use tokio::net::TcpListener;

5. # fn main() {
6. // Set up a listening socket, just like in std::net
7. let addr = "127.0.0.1:12345".parse().unwrap();
8. let listener = TcpListener::bind(&addr)
9. .expect("unable to bind TCP listener");

11. // Listen for incoming connections.
12. // This is similar to the iterator of incoming connections that
13. // .incoming() from std::net::TcpListener, produces, except that
14. // it is an asynchronous Stream of tokio::net::TcpStream instead
15. // of an Iterator of std::net::TcpStream.
16. let incoming = listener.incoming();

18. // Since this is a Stream, not an Iterator, we use the for_each
19. // combinator to specify what should happen each time a new
20. // connection becomes available.
21. let server = incoming
22. .map_err(|e| eprintln!("accept failed = {:?}", e))
23. .for_each(|socket| {
24. // Each time we get a connection, this closure gets called.
25. // We want to construct a Future that will read all the bytes
26. // from the socket, and write them back on that same socket.
27. //
28. // If this were a TcpStream from the standard library, a read or
29. // write here would block the current thread, and prevent new
30. // connections from being accepted or handled. However, this
31. // socket is a Tokio TcpStream, which implements non-blocking
32. // I/O! So, if we read or write from this socket, and the
33. // operation would block, the Future will just return NotReady
34. // and then be polled again in the future.
35. //
36. // While we *could* write our own Future combinator that does an
37. // (async) read followed by an (async) write, we'll instead use
38. // tokio::io::copy, which already implements that. We split the
39. // TcpStream into a read "half" and a write "half", and use the
40. // copy combinator to produce a Future that asynchronously
41. // copies all the data from the read half to the write half.
42. let (reader, writer) = socket.split();
43. let bytes_copied = tokio::io::copy(reader, writer);
44. let handle_conn = bytes_copied.map(|amt| {
45. println!("wrote {:?} bytes", amt)
46. }).map_err(|err| {
47. eprintln!("I/O error {:?}", err)
48. });

50. // handle_conn here is still a Future, so it hasn't actually
51. // done any work yet. We *could* return it here; then for_each
52. // would wait for it to complete before it accepts the next
53. // connection. However, we want to be able to handle multiple
54. // connections in parallel, so we instead spawn the future and
55. // return an "empty" future that immediately resolves so that
56. // Tokio will _simultaneously_ accept new connections and
57. // service this one.
58. tokio::spawn(handle_conn)
59. });

61. // The `server` variable above is itself a Future, and hasn't actually
62. // done any work yet to set up the server. We need to run it on a Tokio
63. // runtime for the server to really get up and running:
64. tokio::run(server);
65. # }

More examples can be found here.

Next up: Reading and Writing Data