Help needed to split line into vec

I want to read a file line by line and split each line into a vector of &str. I will modify the values and cast them into floats later.

Here is my code:

use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufReader};

fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().collect();

    if (args.len() - 1) < 2 {
        println!("Usage:");
        println!("    <program> input_file output_file");
        println!();
        std::process::exit(1);
    }

    let input_file = &args[1];
    let _output_file = &args[2];

    let f = File::open(input_file)?;
    let f = BufReader::new(f);

    for line in f.lines() {
        let l = line.unwrap().split(",");
        let vec: Vec<&str> = l.collect();
        println!("{:?}", vec);
    }

    Ok(())
}

And the error I get:

 cargo run input.csv output.csv
   Compiling kmeans v0.1.0 (/home/labolb/rust_projects/kmeans)
error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:36:17
   |
36 |         let l = line.unwrap().split(",");
   |                 ^^^^^^^^^^^^^           - temporary value is freed at the end of this statement
   |                 |
   |                 creates a temporary which is freed while still in use
37 |         let vec: Vec<&str> = l.collect();
   |                              - borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error: aborting due to previous error

For more information about this error, try `rustc --explain E0716`.
error: Could not compile `kmeans`.

To learn more, run the command again with --verbose.

My input file contains this:

#x,y
1,1.1
1.2,1
2.0,2.4
3.3,5.2

Do I need to make a copy of line first? Am I not using a proper idiom?

I'm not sure how to solve this.

unwrap is obviously bad idiom,
but in your case problem in temporary variable,
you should make it not temporary:

let line = line?;
1 Like

It's working, thanks.

Would this be idiomatic?

    for line in f.lines() {
        let l = line?;
        let l = l.split(",");
        let vec: Vec<&str> = l.collect();
        println!("{:?}", vec);
    }

I don't know idiomatic is too vague conception.
But not bad, except Vec<&str> and collect part.
If you going to deal with String (line) right here then why you can't use split iterator instead of memory allocation.

If you going to pass String (ilne) somewhere with Vec<&str> you can not do it because of borrowing.

So on the first sight Vec<&str> looks strange, but may be in context of other code is not bad.

1 Like

I would probably do this:

for result in f.lines() {
    let line = result?;
    let vec: Vec<&str> = line
       .split(",")
       .collect();
    println!("{:?}", vec);
}

The reason line needs to be on a separate line is that the &strs in the vector need to be stored somewhere. In this case they are stored in the long string in line.

As for Vec<&str> vs Vec<String> it really depends on what you need it for. Using Vec<&str> is more efficient, since you do not need to copy every &str into a String, but it means you can't modify it, and the vec cannot outlive line, since it is borrowing the contents inside line.

1 Like

Thank you both for your explanations and examples.

Here is what I am using now:

    // Variables
    let mut points: Vec<Point> = Vec::new();

    // Read file
    let f = File::open(input_file)?;
    let f = BufReader::new(f);

    for line in f.lines() {
        let l = line?;

        if l.starts_with('#') {
            continue;
        }

        // Get float values from line
        let vec: Vec<f64> = l.split(',')
            .map(|x| x.parse::<f64>().unwrap()).collect();

        // Create Point from it and add to points vec
        let p = Point::new(vec[0], vec[1]);
        println!("{:?}", p);
        points.push(p);
    }

Note that you can also do this:

let split = l.split(',');
let a = split.next().unwrap().parse()?;
let b = split.next().unwrap().parse()?;
assert!(split.next().is_none());

// Create Point from it and add to points vec
let p = Point::new(a, b);

The difference is that this does not allocate memory for the vector, and checks that the line is exactly two integers.

2 Likes