Review noob code: reading lines from stdin


#1

This program reads lines from stdin and prints out the failure code, the multiline failure code string are enclosed between lines “== start ==” & “== end ==”.

I want to get rid of let l = line.unwrap(); assignment in the loop, somehow I feel it’s not rusty.

extern crate regex;

use std::io::{self, BufRead};
use regex::Regex;


fn main() {
    let stdin = io::stdin();
    let start_re = Regex::new(r"^== start ==$").unwrap();
    let finis_re = Regex::new(r"^== end ==$").unwrap();
    let mut failure_str = String::new();
    let mut failure_str_matched = false;
    for line in stdin.lock().lines() {
        let l = line.unwrap();
        if failure_str_matched == false {
            if start_re.is_match(&l) {
                failure_str_matched = true;
            }
        } else {
            if finis_re.is_match(&l) {
                break;
            } else {
                failure_str.push_str(&l);
            }
        }
    }
    println!("Found fail code: \n{}\n", failure_str);
}

#2

You don’t need to use regular expressions, you can just do a simple string compare.

To get rid of the unwrap, you need to ask the question: what do you want to have happen when there’s an error reading from stdin?


#3

Thanks @jethrogb for reviewing - I was wondering if there was a better way than assigning "let l " variable inside the for loop, because I assume that would have some sort of a performance penalty, right?

I guess if there would be an error reading stdin I would want it to panic & exit… hmm probably a graceful handling would be better, using a match and then exiting with an error :thinking:


#4

I wanted to try to show you a more rustic way. It is not necessary better in a strict sense, but it is surely a different approach:

use std::io::{self, BufRead};

fn main() {
    let stdin = io::stdin();
    const START_LINE: &str = "== start ==";
    const END_LINE: &str = "== end ==";

    let failure_str = stdin
        .lock()
        .lines()
        .filter_map(|x| x.ok())
        .skip_while(|line| line != START_LINE)
        .skip(1)
        .scan(Some(String::new()), |acc, line| {
            if line == END_LINE {
                Some(acc.take())
            } else {
                acc.as_mut().unwrap().push_str(&line);
                Some(None)
            }
        })
        .filter_map(|x| x)
        .next();

    if let Some(failure_str) = failure_str {
        println!("Found fail code: \n{}\n", failure_str);
    }
}

In this way the failure_str is an Option (I left the original name, but it is obviously misleading in this case), so you know if it has been found or not.


#5

@dodomorandi - wow :exploding_head: ! thanks much appreciated :+1:


#6

I simplified the code a bit – there was no reason at all to use a Cell, but I was tired :sweat_smile:.

A simple Option is used as state of scan, in this way the string can be taken and the None values filtered out by filter_map.