I almost understand this 'does not live long enough' error


#1

I feel like I’ve almost got a grip of lifetimes, however this has stumped me. Today I followed the Guessing Game tutorial, and then decided to make it web based. I grabbed the tiny_http package and got to work. Everything was going well except that all of the code was living inside of the main function. I needed to refactor.

Only now, I get this error:

src/main.rs:30:5: 30:9 error: `data` does not live long enough
src/main.rs:30     data.split('=').collect()
                   ^~~~
src/main.rs:25:66: 31:2 note: reference must be valid for the anonymous lifetime #1 defined on the block at 25:65...
src/main.rs:25 fn parse_fields_from_request(request: &mut Request) -> Vec<&str> {
src/main.rs:26     let mut data = String::with_capacity(request.body_length().unwrap());
src/main.rs:27 
src/main.rs:28     request.as_reader().read_to_string(&mut data).unwrap();
src/main.rs:29 
src/main.rs:30     data.split('=').collect()
               ...
src/main.rs:26:74: 31:2 note: ...but borrowed value is only valid for the block suffix following statement 0 at 26:73
src/main.rs:26     let mut data = String::with_capacity(request.body_length().unwrap());
src/main.rs:27 
src/main.rs:28     request.as_reader().read_to_string(&mut data).unwrap();
src/main.rs:29 
src/main.rs:30     data.split('=').collect()
src/main.rs:31 }

I think that it’s expecting data to live as long as the request, but I’m not sure what to do about it:

extern crate rand;
extern crate tiny_http;

use rand::Rng;
use std::io::Error as IoError;
use std::cmp::Ordering;
use tiny_http::{Server, Request, Response};

fn respond(request: Request, html: String) -> Result<(), IoError> {
    let response = Response::from_string(html)
        .with_header(tiny_http::Header {
            field: "Content-Type".parse().unwrap(),
            value: "text/html".parse().unwrap()
        });

    let result = request.respond(response);

    result
}

fn parse_guess_from_form_request(fields: Vec<&str>) -> &str {
    fields.last().unwrap()
}

fn parse_fields_from_request(request: &mut Request) -> Vec<&str> {
    let mut data = String::with_capacity(request.body_length().unwrap());

    request.as_reader().read_to_string(&mut data).unwrap();

    data.split('=').collect()
}

fn main() {
    let server = Server::http("0.0.0.0:9090").unwrap();
    let mut secret_number = rand::thread_rng().gen_range(1, 101);
    let form = String::from("<form method=\"post\"><input name=\"guess\" type=\"number\" /><button>Submit</button></form>");

    println!("Listening on port 9090.");

    for mut request in server.incoming_requests() {
        let mut html = String::new();
        let fields = parse_fields_from_request(&mut request);

        let guess = match request.body_length() {
            // Parse the guess from the form data:
            Some(_) =>          parse_guess_from_form_request(fields),

            // Show the initial entrance to the game:
            None =>             {
                                    html.push_str("Guess a number between 1 and 100:</p>");
                                    html.push_str(form.as_str());

                                    respond(request, html)
                                        .expect("Failed to send the response.");

                                    continue;
                                }
        };

        let guess: u32 = match guess.trim().parse() {
            // Parse the guess into an unsigned integer:
            Ok(num) =>          num,

            // Show an error because it's not a number:
            Err(_) =>           {
                                    html.push_str("That isn't actually a number...</p>");
                                    html.push_str(form.as_str());

                                    respond(request, html)
                                        .expect("Failed to send the response.");

                                    continue;
                                }
        };

        html.push_str(format!("<p>You guessed {}", guess).as_str());

        match guess.cmp(&secret_number) {
            Ordering::Less =>   {
                                    html.push_str(" which is too small...</p>");
                                    html.push_str(form.as_str());
                                },
            Ordering::Greater => {
                                    html.push_str(" which is too big...</p>");
                                    html.push_str(form.as_str());
                                },
            Ordering::Equal =>  {
                                    html.push_str(" correctly! Guess a new number between 1 and 100:</p>");
                                    html.push_str(form.as_str());

                                    // Create a new secret number:
                                    secret_number = rand::thread_rng().gen_range(1, 101);
                                }
        }

        respond(request, html)
            .expect("Failed to send the response.");
    }
}

Please help this Rust noob understand what is going on, and feel free to point out anything else I’m doing wrong.


#2

You have a request, and you create a String called data from it. Then you split that string at = and return a Vector of string slices.

Question is: Who owns data? What happens to it when parse_fields_from_request returns? It is being created in your method, but then only references to it are returned.


#3

Yeah, parse_fields_from_request should own data, but I’d like it to return the value I’ve split from data and not a reference to it. But I’m not clear on how to do that, I think I’ve missed something when reading.


#4

Functions (like parse_fields_from_request) don’t own things, data structures do.

I’m in a hurry right now, but maybe you can create a struct that has data (as String) but also a view into that field which is your Vector? Then parse_fields_from_request can return that.

Alternatively, split this into two functions, read_request_to_string and parse_fields_from_string, and deal with this in main, like let data = read_request_to_string(&mut request); let fields = parse_fields_from_string(&data).


#5

Thanks for the help @killercup, I added a struct as you said and got it working smoothly:

extern crate rand;
extern crate tiny_http;

use rand::Rng;
use std::io::Error as IoError;
use std::cmp::Ordering;
use tiny_http::{Server, Request, Response};

enum RequestDataError {
    Empty
}

struct RequestData {
    data:       String
}

impl RequestData {
    pub fn new(request: &mut Request) -> Result<RequestData, RequestDataError> {
        let data = match request.body_length() {
            Some(length) => {
                                let reader = request.as_reader();
                                let mut data = String::with_capacity(length);

                                reader.read_to_string(&mut data).unwrap();

                                data
                            },
            None =>         String::from("")
        };

        match data.is_empty() {
            false =>        Ok(RequestData {
                                data: data
                            }),
            true =>         Err(RequestDataError::Empty)
        }
    }

    pub fn guess(&self) -> String {
        self.data.split('=')
            .map(String::from)
            .collect::<Vec<String>>()
            .last()
            .unwrap()
            .to_owned()
    }
}

fn respond(request: Request, html: String) -> Result<(), IoError> {
    let response = Response::from_string(html)
        .with_header(tiny_http::Header {
            field: "Content-Type".parse().unwrap(),
            value: "text/html".parse().unwrap()
        });

    let result = request.respond(response);

    result
}

fn main() {
    let server = Server::http("0.0.0.0:9090").unwrap();
    let mut secret_number = rand::thread_rng().gen_range(1, 101);
    let form = String::from("<form method=\"post\"><input name=\"guess\" type=\"number\" /><button>Submit</button></form>");

    println!("Listening on port 9090.");

    for mut request in server.incoming_requests() {
        let mut html = String::new();

        let guess = match RequestData::new(&mut request) {
            // Parse the guess from the form data:
            Ok(data) =>         data.guess(),

            // Show the initial entrance to the game:
            Err(_) =>           {
                                    html.push_str("Guess a number between 1 and 100:</p>");
                                    html.push_str(form.as_str());

                                    respond(request, html)
                                        .expect("Failed to send the response.");

                                    continue;
                                }
        };

        let guess: u32 = match guess.trim().parse() {
            // Parse the guess into an unsigned integer:
            Ok(num) =>          num,

            // Show an error because it's not a number:
            Err(_) =>           {
                                    html.push_str("That isn't actually a number...</p>");
                                    html.push_str(form.as_str());

                                    respond(request, html)
                                        .expect("Failed to send the response.");

                                    continue;
                                }
        };

        html.push_str(format!("<p>You guessed {}", guess).as_str());

        match guess.cmp(&secret_number) {
            Ordering::Less =>   {
                                    html.push_str(" which is too small...</p>");
                                    html.push_str(form.as_str());
                                },
            Ordering::Greater => {
                                    html.push_str(" which is too big...</p>");
                                    html.push_str(form.as_str());
                                },
            Ordering::Equal =>  {
                                    html.push_str(" correctly! Guess a new number between 1 and 100:</p>");
                                    html.push_str(form.as_str());

                                    // Create a new secret number:
                                    secret_number = rand::thread_rng().gen_range(1, 101);
                                }
        }

        respond(request, html)
            .expect("Failed to send the response.");
    }
}