Search code examples
rustwhile-looplet

While let does not stop even when the while's condition is fulfilled


I'm new to rust, and I'm trying to code a program that opens a websocket for 10 seconds, receive the data from it and then stops. The piece of code is the following.

let now = Instant::now();
while let n=now.elapsed().as_secs() < 10  {
    let msg = socket.read_message().expect("Error reading message");
    let msg = match msg {
        tungstenite::Message::Text(s) => { s }
        _ => { panic!() }
    };
    let parsed: serde_json::Value = serde_json::from_str(&msg).expect("Can't parse to JSON");
    let price_str=parsed["p"].as_str().unwrap();
    let price: f32 = price_str.parse().unwrap();
    

    write!(f,"1 \t").expect("unable to write");
    write!(f, "\t\t {} \n", price).expect("unable to write");
    println!("{}",n);
    
}

n becomes false after 10 seconds, but the loop never ends. What I'm doing wrong?

Thanks for your help.


Solution

  • while let n binds the result of the expression now.elapsed().as_secs() < 10 to n. This binding can never fail, thus your loop never exits.

    The compiler emits a lint to prevent such mistakes:

    warning: irrefutable `while let` pattern
      --> src/lib.rs:24:11
       |
    24 |     while let n = now.elapsed().as_secs() < 10 {
       |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       |
       = note: `#[warn(irrefutable_let_patterns)]` on by default
       = note: this pattern will always match, so the loop will never exit
       = help: consider instead using a `loop { ... }` with a `let` inside it
    

    To fix your snippet, you'll need to remove the let n part. Or in a more unusual and rather unidiomatic manner, you can pattern match on the value returned by now.elapsed().as_secs() < 10 through:

    while let true = now.elapsed().as_secs() < 10 {
        // do your thing
    }
    

    If you want access to the loop control variable, you can still bind it to a variable through:

    let now = std::time::Instant::now();
    while let n @ true = now.elapsed().as_secs() < 10 {
        println!("loop_control={}", n)
    }
    

    As @Jmb mentions in a comment, there is another issue that's not a compiler error: The loop body may block indefinitely, thus rendering the timeout ineffective.