Can't figure out how to filter content of a file

Hello everybody,

currently, I am facing a problem, that I can't solve myself. I have a file and read the content into a Vec<&str>. The file contains a bunch of lines with hostname and MAC address as follows:

hostname1 aa:bb:cc:dd:ee:ff
hostname2 00:11:22:33:44:55 

Now, I am looking for a way to get the MAC address of a specific hostname.
I was thinking about two possible solutions:

  1. Parsing into HashMap<hostname, MAC>

My first approach was to simply parse each line into a hash map and then get the requested MAC address by running hashmap.get(hostname)
The problem I am facing with this solution is the lifetime of the variables as you can see in the code:

fn run(cfg: &Config) -> Result<(), Box<dyn Error>> {
    let mut file = File::open(HOST_FILE)?;
    let lines: Vec<_> = BufReader::new(file).lines().collect::<Result<_, _>>().unwrap();

    if lines.len() <= 0 {
        panic!("No hosts configured");
    }

    // parse HOST_FILE lines into host hash map
    let mut hosts: HashMap<&str, [u8; 6]> = HashMap::new();
    for l in lines {
        let (host, mac) = parse_host(l.as_str())?;
        hosts.insert(host, mac);
    }

    let mac = hosts.get(&cfg.host.as_str()).ok_or(||
        panic!(format!("Host {} is not configured", cfg.host)));

    Ok(())
}

The loop has the variable l, and this variable will be parsed in the parse_host(&str) function. The return values are inserted into the hash map hosts. And this is where the compiler aborts. 'l' does not live long enough.

  1. Filtering the Vec<&str>

After failing implementing the first approach, I was thinking about a cleaner solution. Instead of having the overhead of parsing every line into a hash map, I actually just need to parse a single line, namely that line with the corresponding hostname. All of the other files are irrelevant and doesn't need to be stored in a hash map.
But also here, I fail to implement it.

Personally, I prefer implementing the second solution, but nevertheless, I am interested in also having a solution for my first issue (to improve my rust knowledge).

So, the things I have at the moment are the lines of the file in a Vec<&str> and I need to filter these lines. I tried to do it like this:

let tmp = lines.into_iter().filter(|l| l.contains(cfg.host));

Unfortunately, I fail in any further step. Can someone help me to extract only the line which contains the requested hostname? This line will be parsed to a hostname and a MAC address, and the MAC will be data I need, whereas the hostname is the data I know while executing the program.

I will be thankful for any helpful answer.

Best regards!

As far as 2 goes, there's two ways you could do this. You can collect the reader into lines and then filter over them:

let mut file = File::open(HOST_FILE)?;

let lines = BufReader::new(file)
    .lines()
    .collect::<Result<Vec<_>, _>>()?;
        
let mac = lines
    .iter()
    .filter_map(|l| parse_host(l).ok())
    .find(|(h, _)| *h == &cfg.host)
    .ok_or_else(|| format!("Host {} is not configured", cfg.host))?
    .1;

This does require collecting into a Vec, which probably isn't that bad but you can get around it by finding the mac in the original lines iterator:

let mac = BufReader::new(file)
    .lines()
    .filter_map(|l| l.ok())
    .find_map(|l| {
        let (h, mac) = parse_host(&l).ok()?;
        if h == &cfg.host {
            Some(String::from(mac))
        } else {
            None
        }
    })
    .ok_or_else(|| format!("Host {} is not configured", host))?;

This is a little more nasty because you have to keep ownership of the String so you can't just turn the iterator into a vector of (host, mac) pairs, unless your parse_host function can return a pair of owned strings.

These are different than your original HashMap version in that if they fail to parse, they continue going instead of terminating immediately. If this isn't desired, you can just do one call to filter_map/find_map and do all the logic in there, returning a Result<&str, Box<dyn Error>> and then try_fold it.

2 Likes

Amazing how smooth it works.
I hope, I'll gain this rust knowledge soon. Thanks a lot for your answer!

Note that you don't have Vec<&'a str> anywhere. The lines is Vec<String>.

https://doc.rust-lang.org/std/io/struct.Lines.html