Clippy is mad at me. I don't know why

In my first ever use of tokio I have a bunch of async functions that spawn threads that should run forever. They have signatures like so:

async fn socket_listener_task(
    mut tcp_listener: TcpListener,
    serial_writer: SerialWriter,
    mut clients: Clients,
) -> tokio::task::JoinHandle<()> {
    tokio::spawn(async move {
        loop {
            // Do stuff...
        }
    })
}

If any of these threads should die so should my whole program. So I thought I would use tokio::select! to catch when any one terminates:

    tokio::select! {
        _ = nats_pinger(nc.clone()).await => {
            println!("NATS pinger task failed");
        }
        _ = nats_subscriber(nc.clone(), serial_writer.clone()).await => {
            println!("NATS subscriber task failed");
        }
        _ = socket_listener_task(tcp_listener, serial_writer.clone(), clients.clone()).await => {
            println!("NATS socket listener task failed");
        }
        _ = serial_reader_task(serial_reader.clone(), clients.clone(), nc.clone()).await => {
            println!("Serial reader task failed");
        }
    }

As soon as I put that in clippy threw a hissy fit with 12 warnings like so:

warning: unsequenced read of a variable
   --> src/lib.rs:302:13
    |
302 |         _ = socket_listener_task(tcp_listener, serial_writer.clone(), clients.clone()).await => {
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
note: whether read occurs before this write depends on evaluation order
   --> src/lib.rs:305:13
    |
305 |         _ = serial_reader_task(serial_reader.clone(), clients.clone(), nc.clone()).await => {
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#eval_order_dependence

I read the suggested link. I get the idea of confusing order of operations. But I don't understand it here.

I can keep clippy happy by doing this:

// Run the threads!
    let h1 = nats_pinger(nc.clone()).await;
    let h2 = nats_subscriber(nc.clone(), serial_writer.clone()).await;
    let h3 = socket_listener_task(tcp_listener, serial_writer.clone(), clients.clone()).await;
    let h4 = serial_reader_task(serial_reader.clone(), clients.clone(), nc.clone()).await;

    tokio::select! {
        _ = h1 => {
            println!("NATS pinger task failed");
        }
        _ = h2 => {
            println!("NATS subscriber task failed");
        }
        _ = h3 => {
            println!("NATS socket listener task failed");
        }
        _ = h4 => {
            println!("Serial reader task failed");
        }
    }

But that seems yukky.

Any suggestions?

If all your function does is spawn a task, it should not be async. You should only mark a function async if it contains an .await directly. Note also that you are ignoring the output of the JoinHandle (which returns a Result as it will catch panics).

I'm guessing the clippy error is due to how the select! macro is expanded.

2 Likes

This looks like a false positive or at least a very misleading lint description. This can be reduced to:

#[tokio::main]
async fn main() {
    let x = async {};
    let y = async {};
    
    (x.await, y.await);
}

Clippy doesn't like awaiting on two futures in a tuple, and this is what your code does.

Usually you don't want to put await at this location. Futures passed to tokio::select! don't need to be awaited because the whole point of select! is to "await" all of them until one of them is ready. Your code only compiles because you constructed a future that resolves to another future, which is usually not what you want.

1 Like

(and why I recommended not making it async)

Ah, OK.

So I removed the async from my thread starter functions. They were junk hanging around after I refactored things and moved any direct .awaits from the functions.

Now my thread starter looks like:

    // Run the threads!
    let res = tokio::select! {
        res = nats_pinger(nc.clone()) => {
            println!("NATS pinger task failed: {:?}", res);
            res
        }
        res = nats_subscriber(nc.clone(), serial_writer.clone()) => {
            println!("NATS subscriber task failed: {:?}", res);
            res
        }
        res = socket_listener_task(tcp_listener, serial_writer.clone(), clients.clone()) => {
            println!("NATS socket listener task failed: {:?}", res);
            res
        }
        res = serial_reader_task(serial_reader.clone(), clients.clone(), nc.clone()) => {
            println!("Serial reader task failed: {:?}", res);
            res
        }
    }?;

and I have a very happy clippy :slight_smile:

Perhaps too happy. If I remove that sneaky little "?" hiding down there at the end clippy does not warn of the unused "res".

Thanks all.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.