Subset of Lines

I am trying to get a subset of a Lines object. Ideally between indices a and b, but for now I'm just trying to get the first n lines.

I have tried slicing, using take and iterating using a loop, but it seems that Lines isn't the same as a normal iterator. See the following MWE with some of my attempts:

use std::env;
use std::fs;
use std::str::Lines;

fn main() {
    let args: Vec<String> = env::args().collect();
    let filename = &args[1];
    let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");
    let contents_lines = contents.lines();
    println!("{:?}", dummy_function1(contents_lines, 5));
    println!("{:?}", dummy_function2(contents_lines, 5));
    println!("{:?}", dummy_function3(contents_lines, 5));
}

fn dummy_function1(lines: Lines, n: usize) {
    return lines[..n];
}

fn dummy_function2(lines: Lines, n: usize) {
    return lines.take(n);
}

fn dummy_function3(lines: Lines, n: usize) {
    let output = Vec::new();
    for (i, l) in lines.enumerate() {
        if i < n {
            output.push(l);
        } else {
            break;
        }
    }
    return;
}

Thank you in advance!

With the "take", you probably need unwrap items and collect. Here's the code I was using recently and it worked for me, hope it helps:

pub fn get_top_lines(path: &Path, size: usize) -> Vec<String> {
    let input = match File::open(path) {
        Ok(file) => file,
        Err(_) => {
            error!("Error opening file {}", path.display());
            return vec![];
        }
    };
    BufReader::new(input)
        .lines()
        .take(size)
        .map(|item| item.unwrap())
        .collect()
}

None of these functions return anything. you need to declare the return type explicitly with -> ReturnType. For example:

fn dummy_function(lines: Lines, n: usize) -> Vec<String> {
    /* ... */
}
1 Like

Thank you for your quick reply!
I have changed my function to

fn dummy_function(lines: Lines, n: usize) {
    return lines.take(n).map(|item| item.unwrap()).collect();
}

But now I get the following error:

no method named `unwrap` found for reference `&str` in the current scope
method not found in `&str`

Ah, thank you! I didn't realise that I needed to make the return type explicit because my linter wasn't complaining about it, I should have read the documentation better :sweat_smile:

use std::env;
use std::fs;
use std::str::Lines;

fn main() {
    let args: Vec<String> = env::args().collect();
    let filename = &args[1];
    let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");
    let contents_lines = contents.lines();
    //println!("{:?}", dummy_function1(contents_lines, 5));
    // the debug printout of this one is not particularly nice,
    // but using it as an iterator should works:
    println!("{:?}", dummy_function2(contents_lines, 5));
    
    // don’t re-use the same `Lines` iterator:
    let contents_lines = contents.lines();
    println!("{:?}", dummy_function3(contents_lines, 5));
}

/* this can’t work
fn dummy_function1(lines: Lines, n: usize) {
    return lines[..n];
}
*/

use std::iter;
// was missing return type
fn dummy_function2(lines: Lines<'_>, n: usize) -> iter::Take<Lines<'_>> {
    return lines.take(n);
}

// was missing return type
fn dummy_function3(lines: Lines<'_>, n: usize) -> Vec<&str> {
    let mut output = Vec::new();
    for (i, l) in lines.enumerate() {
        if i < n {
            output.push(l);
        } else {
            break;
        }
    }
    return output;
}

(playground)

2 Likes

Rust intentionally doesn't provide global type inference. Function items and methods always need to declare argument and return types explicitly. Inferring either of these only works for closures.

Also consider not using an explicit return at the end of your functions. Rust is an expression-based language – the last expression of a block is its value, and this also applies to functions (as well as other block-bodied expressions, like if…else and match).

Another option is to use an opaque return type:

fn dummy_function2(lines: Lines<'_>, n: usize) -> impl Iterator<Item = &str> {
    return lines.take(n);
}
println!("{:?}", dummy_function2(contents_lines, 5).collect::<Vec<_>>());

Returning a Vec should be avoided if you don’t need it, e.g. when all you need to do is iterate over the resulting iterator, since it involves additional allocation.

3 Likes

Adding to what you've learned from the other replies, this can be achieved with

line_iterator.skip(a).take(b-a)