Timeouts in Rust

Timeouts are important for programs that connect to external resources or that otherwise need to bound execution time. Implementing timeouts in Rust is straightforward using channels and select! macro.

use std::time::Duration;
use std::thread;
use tokio::time;
use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    // For our example, suppose we're executing an external
    // call that returns its result on a channel `tx1`
    // after 2s. Note that the channel is buffered, so the
    // send in the thread is nonblocking. This is a
    // common pattern to prevent thread leaks in case the
    // channel is never read.
    let (tx1, mut rx1) = mpsc::channel(1);
    thread::spawn(move || {
        thread::sleep(Duration::from_secs(2));
        let _ = tx1.blocking_send("result 1".to_string());
    });

    // Here's the `select!` implementing a timeout.
    // `rx1.recv().await` awaits the result and `time::sleep(Duration::from_secs(1)).await`
    // awaits for the timeout of 1s. Since `select!` proceeds with the first
    // future that's ready, we'll take the timeout case
    // if the operation takes more than the allowed 1s.
    tokio::select! {
        res = rx1.recv() => {
            println!("{}", res.unwrap());
        }
        _ = time::sleep(Duration::from_secs(1)) => {
            println!("timeout 1");
        }
    }

    // If we allow a longer timeout of 3s, then the receive
    // from `rx2` will succeed and we'll print the result.
    let (tx2, mut rx2) = mpsc::channel(1);
    thread::spawn(move || {
        thread::sleep(Duration::from_secs(2));
        let _ = tx2.blocking_send("result 2".to_string());
    });

    tokio::select! {
        res = rx2.recv() => {
            println!("{}", res.unwrap());
        }
        _ = time::sleep(Duration::from_secs(3)) => {
            println!("timeout 2");
        }
    }
}

Running this program shows the first operation timing out and the second succeeding.

$ cargo run
timeout 1
result 2

In this Rust version, we use the tokio runtime for asynchronous operations. The select! macro from tokio is used to implement the timeout mechanism, similar to Go’s select statement. We use mpsc channels for communication between threads, and tokio::time::sleep for creating timeouts.

Note that Rust’s approach to concurrency is slightly different from Go’s. Instead of goroutines, we use Rust’s standard threads and async tasks. The tokio::main attribute sets up the tokio runtime for our main function.