I'm writing a code to read data from text file in Rust. The target files have two columns and non-fixed length rows, for example,


1  1
2  4
3  9
4  16

To achieve this, I wrote following code.

use std::fs::read_to_string;

pub fn open_file(filename: &str) -> (Vec<f32>, Vec<f32>){
    let data = read_to_string(filename);
    let xy = match data {
        Ok(content) => content,
        Err(error) => {panic!("Could not open or find file: {}", error);}

    let xy_pairs: Vec<&str> = xy.trim().split("\n").collect();
    let mut x: Vec<f32> = Vec::new();
    let mut y: Vec<f32> = Vec::new();

    for pair in xy_pairs {
        let p: Vec<&str> = pair.trim().split(" ").collect();


    (x, y)

I'm a beginner of Rust, so I don't know that I follow the best practice in Rust and this is the most efficient code.

I want some advice about this function.

  • \$\begingroup\$ You should copy your already working code here and then don't modify it manually. That's a general rule. You didn't follow it because your code cannot compile as-is with the unknown f33 type in line 3. Since fixing this typo doesn't invalidate any existing answer you may still do that. \$\endgroup\$ Oct 23, 2019 at 17:33

1 Answer 1


I honestly think you should be breaking up the treatment of the file into two parts, as you are already (implicitly) doing so in your function. One part to read the file into a line iterator, another to actually parse.

The "reading into an iterator" is a simple combination of std::fs::File::read_line; I'll instead focus on the other part:

pub fn read_xy_pairs(i: impl Iterator<Item = String>) -> (Vec<f32>, Vec<f32>) {
        .map(|string| -> Vec<f32> {
            string.split(" ")
                .map(|element| element.parse::<f32>())
                .filter(|element| element.is_ok())
                .map(|element| element.unwrap())
        .filter(|item| item.len() == 2)
        .map(|mut item| (item.swap_remove(0), item.swap_remove(0)))

The processing is clearly laid out, in order:

  1. I convert each element of the iterator, in turn, into an iterator of &str via String::split()
  2. I then truncate this iterator to only take the first two elements lazily
  3. Each element of this iterator gets parsed() and I filter any element that isn't okay, unwrapping those that are
  4. This iterator gets collected into a Vec<f32>
  5. From there, I filter out any element that does not have exactly 2 components, and convert those that do into a tuple2 of (f32, f32)
  6. I then unzip this iterator of (f32, f32) into a (Vec<f32>, Vec<f32>) to fit your type signature
  • \$\begingroup\$ Thank you for your polite answer, but I have some questions. i) you use map twice (one with arrow operator (?) and another without), what is the difference? ii) I cannot understand the behavior of swap_remove enough. Could you teach me more or give me some references? \$\endgroup\$
    – Y. P
    Oct 23, 2019 at 14:28
  • \$\begingroup\$ Hey @Y.P, and sorry about the delay. In order, 1) is not an arrow operator but a type definition. I am specifically telling the compiler the map closure will output a Vec<_> in order to not have to store it as an intermediate variable for collect() to be able to infer the return type. ii) swap_remove() removes the element at position N and returns it. As I am removing items, I need to make sure I remove the first one twice (as the second element will have shifted to position 1 after the first removal). Let me know if you have any other questions! \$\endgroup\$ Oct 24, 2019 at 14:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.