How do I avoid obfuscating logic in a `loop`?

Viewed 161

Trying to respect Rust safety rules leads me to write code that is, in this case, less clear than the alternative.

It's marginal, but must be a very common pattern, so I wonder if there's any better way.

The following example doesn't compile:

async fn query_all_items() -> Vec<u32> {
    let mut items = vec![];
    let limit = 10;

    loop {
        let response = getResponse().await;

        // response is moved here
        items.extend(response);

        // can't do this, response is moved above
        if response.len() < limit {
            break;
        }
    }

    items
}

In order to satisfy Rust safety rules, we can pre-compute the break condition:

async fn query_all_items() -> Vec<u32> {
    let mut items = vec![];
    let limit = 10;

    loop {
        let response = getResponse().await;

        let should_break = response.len() < limit;

        // response is moved here
        items.extend(response);

        // meh
        if should_break {
            break;
        }
    }

    items
}

Is there any other way?

4 Answers

I agree with Daniel's point that this should be a while rather than a loop, though I'd move the logic to the while rather than creating a boolean:

let mut len = limit;
while len >= limit {
    let response = queryItems(limit).await?;

    len = response.len();

    items.extend(response);
}

Not that you should do this, but an async stream version is possible. However a plain old loop is much easier to read.

use futures::{future, stream, StreamExt}; // 0.3.19
use rand::{
    distributions::{Distribution, Uniform},
    rngs::ThreadRng,
};
use std::sync::{Arc, Mutex};
use tokio; // 1.15.0

async fn get_response(rng: Arc<Mutex<ThreadRng>>) -> Vec<u32> {
    let mut rng = rng.lock().unwrap();
    let range = Uniform::from(0..100);
    let len_u32 = range.sample(&mut *rng);
    let len_usize = usize::try_from(len_u32).unwrap();
    vec![len_u32; len_usize]
}

async fn query_all_items() -> Vec<u32> {
    let rng = Arc::new(Mutex::new(ThreadRng::default()));
    stream::iter(0..)
        .then(|_| async { get_response(Arc::clone(&rng)).await })
        .take_while(|v| future::ready(v.len() >= 10))
        .collect::<Vec<_>>()
        .await
        .into_iter()
        .flatten()
        .collect()
}

#[tokio::main]
async fn main() {
    // [46, 46, 46, ..., 78, 78, 78], or whatever random list you get
    println!("{:?}", query_all_items().await);
}

I would do this in a while loop since the while will surface the flag more easily.

fn query_all_items () -> Vec<Item> {
    let items = vec![];
    let limit = 10;
    let mut limit_reached = false;
    
    while limit_reached {
        let response = queryItems(limit).await?;

        limit_reached = response.len() >= limit;
    
        items.extend(response);
    }

    items
}

Without context it's hard to advise ideal code. I would do:

fn my_body_is_ready() -> Vec<u32> {
    let mut acc = vec![];
    let min = 10;

    loop {
        let foo = vec![42];

        if foo.len() < min {
            acc.extend(foo);
            break acc;
        } else {
            acc.extend(foo);
        }
    }
}
Related