Tokio: Event-driven, non-blocking I/O platform for Rust

repository·master·Indexed Apr 15, 2026

https://github.com/tokio-rs/tokio

Tokio is an asynchronous runtime for Rust providing a scheduler, reactor, and networking primitives for building high-performance network services. Key features include a multithreaded work-stealing scheduler, async TCP/UDP sockets, and utilities like tokio_util::codec::Framed. The platform supports io_uring for file operations, configurable worker threads via TOKIO_WORKER_THREADS, and max I/O events tuning. LTS versions include 1.47.x and 1.51.x. Examples demonstrate shared state patterns, logging with tracing_subscriber, and Linux file descriptor pre-warming to prevent latency spikes.

Tokens
53.9K
Snippets
208
Records
401
Agent score
99%

What's inside tokio

  1. Use Framed with LinesCodec for Line-Based Protocols

    master

    The chat server uses tokio_util::codec::Framed with LinesCodec to handle line-delimited text over TCP. This abstracts away the raw byte handling and provides a stream of complete lines.

    Setup pattern:

    use tokio_util::codec::{Framed, LinesCodec};
    use tokio::net::TcpStream;
    
    let stream = TcpStream::connect(addr).await?;
    let mut lines = Framed::new(stream, LinesCodec::new());
    
    // Send a line
    lines.send("Hello, world!").await?;
    
    // Receive a line
    if let Some(Ok(line)) = lines.next().await {
        println!("Received: {line}");
    }

    The LinesCodec automatically handles \r\n line delimiters (as required by telnet). The Framed wrapper provides both Sink and Stream implementations, allowing bidirectional communication on the same connection.

    Sources: examples/chat.rs

  2. Use tokio::task::LocalSet for !Send tasks

    master

    Use tokio::task::LocalSet to spawn tasks that are not Send. This is required when running code that borrows non-thread-safe data or uses types that do not implement Send.

    Usage:

    use tokio::task;
    
    #[tokio::main]
    async fn main() {
        let local = task::LocalSet::new();
        local.run_until(async {
            // Spawn !Send tasks here
            task::spawn_local(async {
                // !Send code
            }).await.unwrap();
        }).await;
    }

    This feature was added in version 0.2.1.

    use tokio::task;
    
    #[tokio::main]
    async fn main() {
        let local = task::LocalSet::new();
        local.run_until(async {
            task::spawn_local(async {
                // !Send code
            }).await.unwrap();
        }).await;
    }

    Sources: tokio/CHANGELOG.md

  3. Configure max I/O events per tick

    master

    You can configure the maximum number of I/O events polled from the OS per tick in the Tokio runtime. This is useful for tuning performance or preventing starvation in high-load scenarios.

    Use the max_io_events configuration option when building your runtime:

    use tokio::runtime::Runtime;
    
    let rt = Runtime::builder()
        .max_io_events(1024) // Set max events per tick
        .build()?;

    Alternatively, you can set the environment variable TOKIO_MAX_IO_EVENTS to configure this value for all runtime instances without code changes.

    let rt = Runtime::builder()
        .max_io_events(1024)
        .build()?;

    Sources: tokio/CHANGELOG.md

  4. Use TcpSocket for socket configuration before binding

    master

    Use TcpSocket to configure socket options (like reuseaddr, reuseport, linger, buffer sizes) before binding or listening. This allows you to set options that are not available on TcpStream after it has been created.

    Key methods:

    • TcpSocket::new_v4() / TcpSocket::new_v6(): Create a new socket.
    • TcpSocket::reuseaddr(): Enable address reuse.
    • TcpSocket::reuseport(): Enable port reuse.
    • TcpSocket::local_addr(): Get the local address.
    • TcpSocket::bind(): Bind the socket to an address.
    • TcpSocket::listen(): Start listening for connections.

    This is the recommended way to configure TCP sockets with specific options before accepting connections.

    Sources: tokio/CHANGELOG.md

  5. Configure Logging for the Chat Example

    master

    The chat example uses tracing_subscriber for logging. It is configured to display traces emitted by the example code and allows filtering via the RUST_LOG environment variable.

    To enable additional traces from Tokio itself, set the environment variable before running the example:

    RUST_LOG=tokio=info,cargo=info,chat=info cargo run --example chat

    The example specifically filters for chat=info by default. You can adjust the filter to show debug or trace level logs for the chat server:

    RUST_LOG=chat=debug cargo run --example chat

    The logging configuration includes full span events (creation, entry, exit, close) to track the lifecycle of spawned tasks on the Tokio runtime.

    Sources: examples/chat.rs

  6. Use tokio 0.2.11 macros: select!, join!, try_join!

    master

    Use the select!, join!, and try_join! macros introduced in Tokio 0.2.11 for concurrent execution patterns:

    • select!: Waits for the first branch to complete
    • join!: Waits for all branches to complete
    • try_join!: Waits for all branches to complete or the first error
    use tokio::time::{sleep, Duration};
    
    // select! - first to complete wins
    let result = tokio::select! {
        _ = sleep(Duration::from_millis(100)) => "timer",
        _ = async { "async" } => "async",
    };
    
    // join! - all complete
    let (a, b) = tokio::join!(
        async { 1 },
        async { 2 }
    );
    
    // try_join! - first error wins
    let result = tokio::try_join!(
        async { Ok::<_, ()>(1) },
        async { Err::<(), ()>(()) }
    );

    Sources: tokio/CHANGELOG.md

  7. Build a basic TCP echo server

    master

    Tokio provides a #[tokio::main] macro to set up the runtime for your async main function. Below is a complete example of a TCP echo server that binds to 127.0.0.1:8080, accepts connections, and echoes data back to the client:

    use tokio::net::TcpListener;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let listener = TcpListener::bind("127.0.0.1:8080").await?;
    
        loop {
            let (mut socket, _) = listener.accept().await?;
    
            tokio::spawn(async move {
                let mut buf = [0; 1024];
    
                loop {
                    let n = match socket.read(&mut buf).await {
                        Ok(0) => return,
                        Ok(n) => n,
                        Err(e) => {
                            eprintln!("failed to read from socket; err = {:?}", e);
                            return;
                        }
                    };
    
                    if let Err(e) = socket.write_all(&buf[0..n]).await {
                        eprintln!("failed to write to socket; err = {:?}", e);
                        return;
                    }
                }
            });
        }
    }

    This example uses TcpListener for binding and accepting connections, and AsyncReadExt/AsyncWriteExt for asynchronous I/O operations.

    use tokio::net::TcpListener;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let listener = TcpListener::bind("127.0.0.1:8080").await?;
    
        loop {
            let (mut socket, _) = listener.accept().await?;
    
            tokio::spawn(async move {
                let mut buf = [0; 1024];
    
                loop {
                    let n = match socket.read(&mut buf).await {
                        Ok(0) => return,
                        Ok(n) => n,
                        Err(e) => {
                            eprintln!("failed to read from socket; err = {:?}", e);
                            return;
                        }
                    };
    
                    if let Err(e) = socket.write_all(&buf[0..n]).await {
                        eprintln!("failed to write to socket; err = {:?}", e);
                        return;
                    }
                }
            });
        }
    }

    Sources: README.md

  8. TinyDB protocol commands

    master

    The TinyDB server supports two commands:

    GET $key Fetches the value of $key from the database.

    Example:

    GET foo

    Response:

    foo = bar

    If the key does not exist:

    GET FOOBAR

    Response:

    error: no key FOOBAR

    SET $key $value Sets the value of $key to $value, returning the previous value if any.

    Example:

    SET FOOBAR my awesome string

    Response:

    set FOOBAR = `my awesome string`, previous: None

    Example with existing key:

    SET foo tokio

    Response:

    set foo = `tokio`, previous: Some("bar")

    Commands are case-sensitive. The server parses input by splitting on spaces into up to 3 parts.

    Sources: examples/tinydb.rs

  9. Build a basic TCP echo server

    master

    Create a basic TCP echo server using Tokio's TcpListener and async I/O traits. Ensure you have the full features enabled in your Cargo.toml.

    Use the #[tokio::main] macro to set up the async runtime for your entry point. Use tokio::spawn to handle incoming connections concurrently.

    use tokio::net::TcpListener;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let listener = TcpListener::bind("127.0.0.1:8080").await?;
    
        loop {
            let (mut socket, _) = listener.accept().await?;
    
            tokio::spawn(async move {
                let mut buf = [0; 1024];
    
                // In a loop, read data from the socket and write the data back.
                loop {
                    let n = match socket.read(&mut buf).await {
                        // socket closed
                        Ok(0) => return,
                        Ok(n) => n,
                        Err(e) => {
                            eprintln!("failed to read from socket; err = {:?}", e);
                            return;
                        }
                    };
    
                    // Write the data back
                    if let Err(e) = socket.write_all(&buf[0..n]).await {
                        eprintln!("failed to write to socket; err = {:?}", e);
                        return;
                    }
                }
            });
        }
    }

    Run this example with cargo run.

    use tokio::net::TcpListener;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let listener = TcpListener::bind("127.0.0.1:8080").await?;
    
        loop {
            let (mut socket, _) = listener.accept().await?;
    
            tokio::spawn(async move {
                let mut buf = [0; 1024];
    
                loop {
                    let n = match socket.read(&mut buf).await {
                        Ok(0) => return,
                        Ok(n) => n,
                        Err(e) => {
                            eprintln!("failed to read from socket; err = {:?}", e);
                            return;
                        }
                    };
    
                    if let Err(e) = socket.write_all(&buf[0..n]).await {
                        eprintln!("failed to write to socket; err = {:?}", e);
                        return;
                    }
                }
            });
        }
    }

    Sources: tokio/README.md

  10. Run the TinyDB example server

    master

    The tinydb example demonstrates a simple TCP server with shared in-memory state using Tokio. It implements a key/value database protocol where clients can issue GET and SET commands.

    To run the server:

    cargo run --example tinydb

    By default, the server listens on 127.0.0.1:8080. You can specify a different address as an argument:

    cargo run --example tinydb 192.168.1.10:9000

    The server initializes with a single key foo set to bar.

    Sources: examples/tinydb.rs