Closures That Reference Variables of the Scope

Hello! I am reading topics about Closures of the book "Programming Rust" 2nd Edition. In the section of "Closures That Borrow" presenting a code snippet:

/// Sort by any of several different statistics.
fn sort_by_statistic(cities: &mut Vec<City>, stat: Statistic) {
    cities.sort_by_key(|city| -city.get_statistic(stat));
}

Everything sounds right, but when I write a simple example to exam the code:

fn main() {
    let mut cities = vec![
        City {
            name: String::from("New York"),
            population: 99999,
            area: 89098,
        },
        City {
            name: String::from("Tokyo"),
            population: 9999999,
            area: 8989008,
        },
    ];
    sort_by_statistic(&mut cities, Statistic::Population);

    println!("{:?}", cities);
}

#[derive(Debug)]
struct City {
    name: String,
    population: i64,
    area: i64,
}

impl City {
    fn get_statistic(&self, stat: Statistic) -> i64
    {
        match stat {
            Statistic::Area => self.area,
            Statistic::Population => self.population
        }
    }
}

enum Statistic {
    Area,
    Population,
}

fn sort_by_statistic(cities: &mut Vec<City>, stat: Statistic) {
    cities.sort_by_key(|city| -city.get_statistic(stat));
}

I got this error:

cargo run
Compiling closures v0.1.0 (/home/nd/RustroverProjects/closures)
error[E0507]: cannot move out of stat, a captured variable in an FnMut closure
--> src/main.rs:42:51
|
41 | fn sort_by_statistic(cities: &mut Vec, stat: Statistic) {
| ---- captured outer variable
42 | cities.sort_by_key(|city| -city.get_statistic(stat));
| ------ ^^^^ move occurs because stat has type Statistic, which does not implement the Copy trait
| |
| captured by this FnMut closure

For more information about this error, try rustc --explain E0507.
error: could not compile closures (bin "closures") due to 1 previous error

So one way to make the code compile is to change the implementation of sort_by_statistic and get_statistic
which is

fn sort_by_statistic(cities: &mut Vec<City>, stat: Statistic) {
    cities.sort_by_key(|city| -city.get_statistic(&stat));
}

    fn get_statistic(&self, stat: &Statistic) -> i64
    {
        match stat {
            Statistic::Area => self.area,
            Statistic::Population => self.population
        }
    }

So I am wondering is it a typo of the book, or I am missing something? I mean is there any type whose reference that can be presented without "&"?

Which lead to the frustration the book states about:

In this case, when Rust creates the closure, it automatically borrows a reference to stat. It stands to reason: the closure refers to stat, so it must have a reference to it.

because looking at the code snippet and its explanation, it feels like the closure take the ownership of the value of stat variable and under the hood Rust is smart enough that it will only takes reference.

I don't own that book, so I can't really say anything in reference to that. But closures try to capture as "uninvasively" as possible, trying to capture by reference first and only capturing by either mutable reference or by moving/copying when necessary. In your case it is necessary, because get_statistic takes Statistic by value and not by reference (which you were able to fix by taking stat as reference).

3 Likes

Thanks for your clarification, it is likely a typo and I will continue on reading, the author only store bigger examples code repo, so maybe he overlooked but the text explaining the code -- "automatically borrows a reference to stat" still sounds strange.

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.