Unhelpful error messages with closures

I'm implementing Peter Shirley's Ray Tracing in a Weekend, and I'm running into strange error messages when trying to parallelize rendering using Rayon.

You can a look at the rest of the code, but the important bit is this:

let colors: Vec<Vec3> = (0..ny)
        .into_par_iter()
        .rev()
        .flat_map(|j| {
            (0..nx).into_par_iter().map(|i| {
                let mut rng = rand::thread_rng();
                let mut col = Vec3::new(0.0, 0.0, 0.0);
                for _ in 0..ns {
                    let u = (f64::from(i) + rng.gen::<f64>()) / f64::from(nx);
                    let v = (f64::from(j) + rng.gen::<f64>()) / f64::from(ny);
                    let ray = camera.get_ray(u, v);
                    col = col + color(&ray, &world);
                }
                col / f64::from(ns)
            })
        })
        .collect();

Which gives this error message:

rror[E0373]: closure may outlive the current function, but it borrows `j`, which is owned by the current function
   --> src/main.rs:309:41
    |
309 |             (0..nx).into_par_iter().map(|i| {
    |                                         ^^^ may outlive borrowed value `j`
...
314 |                     let v = (f64::from(j) + rng.gen::<f64>()) / f64::from(ny);
    |                                        - `j` is borrowed here
    |
note: closure is returned here
   --> src/main.rs:309:13
    |
309 | /             (0..nx).into_par_iter().map(|i| {
310 | |                 let mut rng = rand::thread_rng();
311 | |                 let mut col = Vec3::new(0.0, 0.0, 0.0);
312 | |                 for _ in 0..ns {
...   |
318 | |                 col / f64::from(ns)
319 | |             })
    | |______________^
help: to force the closure to take ownership of `j` (and any other referenced variables), use the `move` keyword
    |
309 |             (0..nx).into_par_iter().map(move |i| {
    |                                         ^^^^^^^^

However if I follow the advice and move the closure, I get this:

error[E0507]: cannot move out of captured variable in an `Fn` closure
   --> src/main.rs:309:41
    |
298 |       let world = HittableList {
    |           ----- captured outer variable
...
309 |               (0..nx).into_par_iter().map(move |i| {
    |  _________________________________________^
310 | |                 let mut rng = rand::thread_rng();
311 | |                 let mut col = Vec3::new(0.0, 0.0, 0.0);
312 | |                 for _ in 0..ns {
...   |
318 | |                 col / f64::from(ns)
319 | |             })
    | |_____________^ cannot move out of captured variable in an `Fn` closure

Depending on how else I move around code here, I get other interesting error messages, such as this if I try to collect the internal map:

error[E0282]: type annotations needed
   --> src/main.rs:308:10
    |
308 |         .flat_map(|j| {
    |          ^^^^^^^^ cannot infer type for `PI`

You need the move to take ownership of j, but the problem is that it tries to also take ownership of the captured world. That's not a Copyable type, so it can only be moved once, but the flat_map's Fn will be called many times. You can fix this by make world a reference first, so the move closure is just capturing that explicit reference.

1 Like

Thanks, that clears that up!

Any idea where the error about PI comes from?

PI is a generic parameter of the method:

fn flat_map<F, PI>(self, map_op: F) -> FlatMap<Self, F> where
    F: Fn(Self::Item) -> PI + Sync + Send,
    PI: IntoParallelIterator, 

This is because collect is also generic in what types it can return. Absent any other hint for type inference, the compiler only knows that this type correspond to PI and must implement IntoParallelIterator, but it doesn't know what actual type to use.

You can use the "turbofish" syntax to tell it your intent, like collect::<Vec<_>>(). Here the _ still leaves the exact item type open to inference, but you could fill that in too. Or you can do the same for flat_map, leaving F open to inference since closures are unnameable: flat_map::<_, Vec<_>>(...)

Another way is to use a typed local binding first (let v: Vec<_> = map.collect();), and return that. Some people dislike turbofish and prefer this. There's also an unstable type ascription feature that would let you annotate the type on the expression, like .collect(): Vec<_>.

1 Like