Pass function closure to thread

Hi,

I am trying a closure example from Programming Rust(by Blandy) that no longer compiles:

pub struct City {
        name: String,
        population: i64,
        country: String,
    }
 enum Statistic {
        Population,
    }

fn start_sorting_thread(
        mut cities: Vec<City>,
        stat: Statistic,
    ) -> thread::JoinHandle<Vec<City>> {
        let  key_fn = move |city: &City| -> i64 { -city.get_statistic(stat) };
        thread::spawn(move || {
            cities.sort_by_key(key_fn);
            cities
        })
    }

The error is "expected closure that implements FnMut trait but only found trait FnOnce. This is for both move closures. Make sense but I don't know how to fix it with the correct syntax.
Any ideas?

Thanks,

Frank

First, please format the code in your post according to the pinned post, it makes it easier to read.

Second, the issue there seems to be that get_statistic() takes stat by value, and Statistic does not implement Copy. That means each call to get_statistic() moves stat into the function, so the closure key_fn can only be called once, so it only implements FnOnce. However, Vec::sort_by_key() takes something that implements FnMut, since it calls it repeatedly.

There are two options for solutions. One would be to add #[derive(Copy)] to the Statistic enum, since it is a very small and simple type. The other option, if Statistic couldn't be made Copy for some reason, would be to modify get_statistic() to take a reference to stat instead.

That code also seems to have the problem that it is using Vec without specifying its generic parameter. The signature of start_sorting_thread() should be:

fn start_sorting_thread(
    mut cities: Vec<City>,
    stat: Statistic,
) -> thread::JoinHandle<Vec<City>> {

You can use the edit button under your initial post to add the two lines, one before and one after your code, that will cause it to format as Rust code. Many readers of this forum will bypass your post until it is formatted in accordance with the instructions for new posters that are pinned to the top of the forum. (See the link in the prior post.)

Thanks for the hint!

1 Like

I had to d a little bit more with the City type since it couldn't be made Copy. My hack on the first closure defined below looks pretty inelegant. Compiles but....
Thanks,
Frank

#[derive(Clone)]
pub struct City {
        name: String,
        population: i64,
        country: String,
    }
#[derive(Clone, Copy)]
pub enum Statistic {
        Population,
    }

fn start_sorting_thread(
        mut cities: Vec<City>,
        stat: Statistic,
    ) -> thread::JoinHandle<Vec<City>> {
        let  key_fn =  move |city: &City| -> i64 { let city_clone = city.clone();  -city_clone.get_statistic(&stat) };
        thread::spawn(move || {
            cities.sort_by_key(key_fn);
            cities
        })
    }

I would have get_statistic() take City by reference to avoid the clone there, and taking a reference to Statistic isn't necessary if it is copy, so it's signature should be (inside an impl City block):

fn get_statistic(&self, stat : Statistic) -> i64

It can then be called using city.get_statistic(stat) (full example here).

1 Like

Thanks for you help. It is a bit of a struggle to get a handle on Rust's type system but I think it will be worth it.
Frank

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.