[solved] Borrowing and explicit lifetimes - looping over vectors

Hello,

I'm really new to Rust and also new to this kind of programming, so, i'll present you the code i'm working on (a simple exercise): as i'm currently working on monitoring, I want a simple « check_ram » that simple retrieve used memory and total available memory on a GNU/Linux system.

For that I read /proc/meminfo and parses it with a Regex. Here is the code:

extern crate regex;

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


struct MemResult<'a, 'b> {
    regex: &'a str,
    value: Option<i32>,
    unit: &'b str,
}

impl<'a, 'b> MemResult<'a, 'b> {
    fn update(&mut self, value: i32, unit: &'b str) {
        self.value = Some(value);
        self.unit = unit;
    }

    fn find(&mut self, text: &'b str) {
        let re = Regex::new(self.regex).unwrap();
        let caps = re.captures(text);

        match caps {
            Some(total) => self.update(
                total.at(1).unwrap().parse::<i32>().unwrap(),
                total.at(2).unwrap()
                ),
            _ => (),
        }
    }
}

fn main() {
    let mut mem_total = MemResult {
        regex: "^MemTotal:[ ]+(.*) ([a-zA-Z]+)",
        value: None,
        unit: "kB" };
    let mut mem_free = MemResult {
        regex: "^MemFree:[ ]+(.*) ([a-zA-Z]+)",
        value: None,
        unit: "kB" };

    let f = File::open("/proc/meminfo");
    let r = BufReader::new(f.unwrap());

    let mut mv = vec![&mut mem_total, &mut mem_free];

    for l in r.lines() {
        for v in mv {
            v.find(l.unwrap().as_str());
        }
    }

    for v in mv {
        match v.value {
            Some(val) => println!("{0}", val),
            _ => ()
        }
    }
}

And here are the compile errors, using Cargo:

src/main.rs:52:20: 52:30 error: borrowed value does not live long enough
src/main.rs:52             v.find(l.unwrap().as_str());
                                  ^~~~~~~~~~
note: in expansion of for loop expansion
src/main.rs:51:9: 53:10 note: expansion site
note: in expansion of for loop expansion
src/main.rs:50:5: 54:6 note: expansion site
src/main.rs:39:22: 62:2 note: reference must be valid for the block suffix following statement 0 at 39:21...
src/main.rs:39         unit: "kB" };
src/main.rs:40     let mut mem_free = MemResult {
src/main.rs:41         regex: "^MemFree:[ ]+(.*) ([a-zA-Z]+)",
src/main.rs:42         value: None,
src/main.rs:43         unit: "kB" };
src/main.rs:44 
               ...
src/main.rs:52:13: 52:41 note: ...but borrowed value is only valid for the statement at 52:12
src/main.rs:52             v.find(l.unwrap().as_str());
                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: in expansion of for loop expansion
src/main.rs:51:9: 53:10 note: expansion site
note: in expansion of for loop expansion
src/main.rs:50:5: 54:6 note: expansion site
src/main.rs:52:13: 52:41 help: consider using a `let` binding to increase its lifetime
src/main.rs:52             v.find(l.unwrap().as_str());
                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: in expansion of for loop expansion
src/main.rs:51:9: 53:10 note: expansion site
note: in expansion of for loop expansion
src/main.rs:50:5: 54:6 note: expansion site
src/main.rs:51:18: 51:20 error: use of moved value: `mv` [E0382]
src/main.rs:51         for v in mv {
                                ^~
note: in expansion of for loop expansion
src/main.rs:51:9: 53:10 note: expansion site
note: in expansion of for loop expansion
src/main.rs:50:5: 54:6 note: expansion site
note: `mv` was previously moved here because it has type `collections::vec::Vec<&mut MemResult<'_, '_>>`, which is non-copyable
src/main.rs:52:20: 52:21 error: use of moved value: `l` [E0382]
src/main.rs:52             v.find(l.unwrap().as_str());
                                  ^
note: in expansion of for loop expansion
src/main.rs:51:9: 53:10 note: expansion site
note: in expansion of for loop expansion
src/main.rs:50:5: 54:6 note: expansion site
note: `l` was previously moved here because it has type `core::result::Result<collections::string::String, std::io::error::Error>`, which is non-copyable
src/main.rs:56:14: 56:16 error: use of moved value: `mv` [E0382]
src/main.rs:56     for v in mv {
                            ^~
note: in expansion of for loop expansion
src/main.rs:56:5: 61:6 note: expansion site
src/main.rs:51:18: 51:20 note: `mv` moved here because it has type `collections::vec::Vec<&mut MemResult<'_, '_>>`, which is non-copyable
src/main.rs:51         for v in mv {
                                ^~
note: in expansion of for loop expansion
src/main.rs:51:9: 53:10 note: expansion site
note: in expansion of for loop expansion
src/main.rs:50:5: 54:6 note: expansion site
error: aborting due to 4 previous errors
Could not compile `check_ram`.

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

I tried many things, also re-re-re-re-reading the Rust book, searching the net but I think i need a bit help about borrowing and lifetimes :confused:

Also, i'm currently working with Rust 1.3.0 since Gentoo hasn't already Rust 1.4.0 in ebuilds.

Thanks for reading & help

extern crate regex;

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


struct MemResult {
    regex: &'static str,
    value: Option<i32>,
    unit: String,
}

impl MemResult {
    fn update(&mut self, value: i32, unit: &str) {
        self.value = Some(value);
        self.unit = unit.to_string();
    }

    fn find(&mut self, text: &str) {
        let re = Regex::new(self.regex).unwrap();
        let caps = re.captures(text);

        match caps {
            Some(total) => self.update(
                total.at(1).unwrap().parse::<i32>().unwrap(),
                total.at(2).unwrap()
                ),
            _ => (),
        }
    }
}

fn main() {
    let mem_total = MemResult {
        regex: "^MemTotal:[ ]+(.*) ([a-zA-Z]+)",
        value: None,
        unit: "kB".to_string() };
    let mem_free = MemResult {
        regex: "^MemFree:[ ]+(.*) ([a-zA-Z]+)",
        value: None,
        unit: "kB".to_string() };

    let f = File::open("/proc/meminfo");
    let r = BufReader::new(f.unwrap());

    let mut mv = vec![mem_total, mem_free];

    for l in r.lines() {
        if let Ok(text) = l {
            
            for v in mv.iter_mut() {
                v.find(&text);
            }
            
        }
    }

    for v in mv {
        match v.value {
            Some(val) => println!("{0} {1}", val, v.unit),
            _ => ()
        }
    }
}

Hopefully this doesn't deviate too much from what you wanted. The big things are:

  • Moved the unwrapping of the text lines outside of the "v" loop. Since unwrapping consumes the Result.
  • Changed the lifetime of the regex to static, although it would probably be better to change the type of regex to Regex and just create the Regex once and reuse it in find() since captures() is read-only and doesn't change the state.
  • Changed unit to a String, because the str it's extracted from is destroyed each iteration through your "l" loop, which means you need to copy the characters before they line is freed.

As a side note when passing an &String rust automatically converts it to a &str parameter so you don't need the as_str() method which is unstable.

1 Like

In general I try to stay away from specifying lifetimes other than static. If I do need to specify lifetimes I really look at the code to determine if the speed/memory benefits from not reallocating outweight the added complexity of the code.

1 Like

You might also want to look at the procinfo crate.

1 Like

Indeed! Creating a regex every time you search is very bad juju because it is very slow.

Interestingly, despite regex's API being immutable, it does indeed mutate state internally! :slight_smile:

1 Like

Thanks a lot for that!

I get it for the lifetimes, String, &String and you're right about storing Regex instead of the string an then compile in find.

But, if I may ask another question, what is the difference of calling .unwrap, on text lines, directly or indirectly like in your code?

Also, about the File::open, shouldn't I try!() it? Examples of using a try! with File::open just give me an error. If I use match { Err(e) => bla }, Rust complains with use of partially moved value: f (I understand why).

So I managed with that:

fn err_and_quit(msg: &str) -> ! {
    println!("{0}", msg);
    exit(1)
}

fn main() {
    // wrong file path here
    let f: File = match File::open("/proc/meminf") {
        Ok(file) => file,
        Err(err) => err_and_quit("cannot open file"),
    };
    let r = BufReader::new(f);
}

I got an unused variable warning, is their any good way to get rid of that? I mean, is this method stupid or it just needs to be cleanup a little?

I also found this resource: Error Handling in Rust - Andrew Gallant's Blog I'm going to read it with the greatest interest.

.unwrap() will panic and terminate the application if an Option is not Some or a Result is not Ok. For example/test code that is okay. For actual programs you usually want to log an error and exit gracefully. So I avoid "unwrap" unless I do a check on the enum first. I guess my coding style just bled through there. :smile:

try! is used when your function returns a "Result" object. It can be used in place of the following code:

let f = match File::open("/proc/meminf") {
    Ok(file) => file,
    Err(err) => return Err(err)
}

Since main() doesn't return anything, you are getting a compile error.

As for the unused variable you use underscore like:

let f = match File::open("/proc/meminf") {
    Ok(file) => file,
    Err(_) => error_and_quit("cannot open file")
}

That basically tells the compiler, yes there is a value here, and no, I don't care about it. It's also useful if a method returns a tuple and you don't care about all the values:

fn foo() -> (u32, u64, f32);

let (a, _, b) = foo();

Here you get the first and last value, and throw out the second one.

1 Like

FYI, this got merged into the book: Error Handling (Meta: I do love directing traffic away from my blog! :P)

2 Likes