Specify lifetime for thread inside of loop, and general best practices

I'm working on day 3 of the 2015 advent of code (Day 3 - Advent of Code 2015) and am attempting to convert my solution to use threads. I'd like to split the work at each line of text but am getting lifetime errors at the line. I understand why I'm getting lifetime errors, there's no telling if the thread will last longer than the loop, but I'm not exactly sure what to do about it. And in general, any direction or help in making this solution more idiomatic rust is greatly appreciated!

use std::sync::mpsc;
use std::thread;

fn main() {
    let input = input();
    let mut total = 0;
    let (transmitter, receiver) = mpsc::channel();

    for line in input.lines() {
        let transmitter2 = mpsc::Sender::clone(&transmitter);
        thread::spawn(move || match line_to_dimensions(line) {
            Some((width, height, length)) => {
                let this_box = paper_size(width, height, length);
                transmitter2.send(this_box).unwrap_or(());
            }

            None => (),
        });
    }

    drop(transmitter);
    for received in receiver {
        total = total + received;
    }

    println!("The total is {}.", total);
}

fn line_to_dimensions(line: &str) -> Option<(u32, u32, u32)> {
    let mut dimensions = line.split('x');
    let width = dimensions.next().map(parse_int)??;
    let height = dimensions.next().map(parse_int)??;
    let length = dimensions.next().map(parse_int)??;
    Some((width, height, length))
}

fn parse_int(string: &str) -> Option<u32> {
    u32::from_str_radix(string, 10).ok()
}

fn paper_size(width: u32, height: u32, length: u32) -> u32 {
    let sides = [width * length, height * length, width * length];
    let smallest = sides.iter().min().unwrap();
    2 * sides[0] + 2 * sides[1] + 2 * sides[2] + smallest
}

fn input() -> String {
    String::from(
        "
3x11x24
13x5x19
1x9x27
24x8x21
14x6x11",
    )
}

The error:

error[E0597]: `input` does not live long enough
  --> 2015_3.rs:9:17
   |
9  |     for line in input.lines() {
   |                 ^^^^^ borrowed value does not live long enough
...
27 | }
   | - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

You can't extend the lifetime of input, what you can do is extend the lifetime of each line by converting the string slice (&str) into an owned string (String) by using str::to_string.

for line in input.lines() {
    let line = line.to_string();
    //...
}

The reason for the error is that String::lines returns an iterator that borrows from the String, this borrow only lives as long as the for loop. Threads only take things that live for a 'static lifetime because they could live for longer than the current thread, so 'static is the only valid lifetime that they could take.

By turning the string slice (&str) into an owned string (String), we have extended the lifetime each line to static by decoupling to from the original String, and storing it on the heap.

You could also use one of the scoped-thread APIs, like crossbeam's scope for general threads or rayon's scope in its threadpool. In either case, you'd move the entire loop inside the scope so the borrowed input value outlives all threads.

On the rayon side, you could also do something like:

let total = input.par_lines()
    .map(|line| {
        // ...
    }).sum();

Then you don't need to worry about explicit threads or channeling the results.

1 Like