Function returning Vec - managing its lifetime

Dear rust users,

I'm trying to teach myself rust by writing a useful but small program with it. The program needs to do something with a Vec<&str>. I've managed to write some code as shown at the end of the post. This compiles, but I had to repeat do_something_with_vec(v); twice which is bothering me. Ideally I'd like to call it once like the below.

let v: Vec<&str>;
if matches.is_present("list") {
    // get 'v' from the command line: --list foo,bar,baz
    v = matches.value_of("list").unwrap().split(",").collect();
} else {
    let r = get_vec_from_file(matches.value_of("file").unwrap());
    v = r.iter().map(|s| s.as_ref()).collect(); // error: borrowed value does not live long enough
}
do_something_with_vec(v);

I'd be very grateful if someone gave me a steer on what the correct approach in Rust is.

extern crate clap;

use clap::{App, Arg};
use std::fs;

fn main() {
    let matches = App::new("A test program")
        .arg(
            Arg::with_name("list")
                .short("l")
                .long("list")
                .help("comma-delimited list of items")
                .conflicts_with("file")
                .required_unless("file")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("file")
                .short("f")
                .long("file")
                .required_unless("list")
                .help("file containing items, one per line")
                .takes_value(true),
        )
        .get_matches();

    let v: Vec<&str>;
    if matches.is_present("list") {
        // get 'v' from the command line: --list foo,bar,baz
        v = matches.value_of("list").unwrap().split(",").collect();
        do_something_with_vec(v);
    } else {
        let r = get_vec_from_file(matches.value_of("file").unwrap());
        v = r.iter().map(|s| s.as_ref()).collect();
        do_something_with_vec(v);
        // this compiles but I had to repeat "do_something_with_list(v);" twice -- is
        // there a better way?
    }
}

fn get_vec_from_file(filename: &str) -> Vec<String> {
    let contents = fs::read_to_string(filename).expect("could not open file");
    let mut file_lines: Vec<String> = Vec::new();
    contents
        .lines()
        .for_each(|line| file_lines.push(String::from(line)));
    file_lines
}

fn do_something_with_vec(v: Vec<&str>) {
    for item in v {
        println!("{}", item);
    }
}

Rust's if clause is an expression (similar to C's ternary expression ? :), therefore you can write something like

let v = if true { vec![0, 1, 2] } else { vec![] };

The only limitation is that both caes most return the same type.

So I took the opportunity and rewrote your example.

1 Like

Change this:

let v: Vec<&str>;
if … {
    …
} else {
    let r = get_vec_from_file(matches.value_of("file").unwrap());
    v = r.iter().map(|s| s.as_ref()).collect(); // error: borrowed value does not live long enough
}

To this:

let r;
let v: Vec<&str>;
if … {
    …
} else {
    r = get_vec_from_file(matches.value_of("file").unwrap());
    v = r.iter().map(|s| s.as_ref()).collect(); // error: borrowed value does not live long enough
}

The problem is that variable r had smaller scope than v. If you move it to the outer scope, it'll live long enough.

1 Like