11

I'm trying to parse a file using regexes:

extern crate regex; // 1.0.1

use regex::Regex;

fn example(
    section_header_pattern: Regex,
    section_name: &str,
    mut line: String,
    mut is_in_right_section: bool,
) {
    loop {
        if let Some(m) = section_header_pattern
            .captures(&line)
            .and_then(|c| c.get(1))
        {
            is_in_right_section = m.as_str().eq(section_name);
            line.clear();
            continue;
        }
    }
}

fn main() {}

...but the compiler complains because the RegEx's captures() method has a borrow which endures for the lifetime of the match:

error[E0502]: cannot borrow `line` as mutable because it is also borrowed as immutable
  --> src/main.rs:17:13
   |
13 |             .captures(&line)
   |                        ---- immutable borrow occurs here
...
17 |             line.clear();
   |             ^^^^ mutable borrow occurs here
18 |             continue;
19 |         }
   |         - immutable borrow ends here

By the time I get to line.clear();, I'm done with the Match and would like to clear the buffer and move onto the next line in the file without further processing. Is there a good/clean/elegant/idiomatic solution or do I need to just bite the bullet and introduce a subsequent 'if' block?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
steamer25
  • 9,278
  • 1
  • 33
  • 38
  • Duplicate of http://stackoverflow.com/q/30243606/155423 ? – Shepmaster Jan 11 '17 at 21:50
  • 4
    Unrelated to your question; why do you use `.eq(`? I've seen other people do the same. What's the rationale behind avoiding `==`? – Shepmaster Jan 11 '17 at 21:53
  • @Shepmaster I'm not sure if they're duplicates since the other question is dealing with the else block (which I don't have) and is solved by an early return. – steamer25 Jan 11 '17 at 22:01
  • 1
    @Shepmaster For my part, and probably true for others as well, experience with Java has made us bit gunshy about using == for string comparison. In Java, the == operator compares the memory offset/pointer of the strings instead of a deep comparison of bytes/characters . – steamer25 Jan 11 '17 at 22:04
  • 4
    I was afraid it was Java. It's so strange that a high level language like Java made such a mis-step with `==` while Rust, a much lower level language, favors value equality over reference equality. – Shepmaster Jan 11 '17 at 22:06
  • @Shepmaster Java didn't misstep at all. Value equality can be expensive and doesn't always mean what you think it might, particularly when inheritance gets involved. Explicit value-comparators are often better. Java also built on C which uses reference equality for the `==` operator. Many languages do. – par Jan 11 '17 at 22:37
  • 5
    @par this probably isn't the best venue to argue about it, but that sounds more like "inheritance makes code hard to understand"; I'm not sure how an `equals` method is exempt from any inheritance strangeness that would affect `==`. Expensive operations that do the *correct thing* will always beat out fast operations that don't do what the programmer wants. We have also learned that you cannot just point to C as the gold standard. If that were the case, Java would not exist, much less Rust! It's tricky to say C has reference equality, as it has *value equality of pointers*, which is different. – Shepmaster Jan 11 '17 at 22:47

1 Answers1

9

Short answer: No.

I'm done with the Match

You may be, but the compiler doesn't know that. Specifically, lifetimes are currently bound to the lexical scope they are defined in. The feature you are looking for is called non-lexical lifetimes. It's not stable now, but it's planned to be enabled in the Rust 2018 edition.

As an example:

fn main() {
    let mut s = String::from("hello");

    let matched = &s[..];
    println!("{}", matched);

    s.clear();

    println!("{}", s);
}

A programmer can tell we are done with matched after we print it, but the compiler says that the borrow lasts until the closing }. The fix is to introduce a scope:

fn main() {
    let mut s = String::from("hello");

    {
        let matched = &s[..];
        println!("{}", matched);
    }
    s.clear();

    println!("{}", s);
}

Your case is more insidious, as the decision to clear the string is interwoven with the value of the borrow of the string itself. Something like this would be my first place to reach:

fn main() {
    let mut s = String::from("hello");

    let do_clear;

    {
        let matched = &s[..];
        println!("{}", matched);
        do_clear = matched.contains("ll");
    }

    if do_clear {
        s.clear();
    }

    println!("{}", s);
}

However, your specific case might be able to be transformed to avoid multiple if / if let statements:

let is_in_right_section = section_header_pattern.captures(&line)
    .and_then(|c| c.get(1))
    .map_or(false, |m| m.as_str() == section_name);

if is_in_right_section {
    line.clear();
    continue;
}

Which wouldn't look too bad if you introduce a new type and/or method. As a bonus, there's a place for the Regex to live:

struct Section(Regex);

impl Section {
    fn is(&self, s: &str, section: &str) -> bool {
        self.0
            .captures(s)
            .and_then(|c| c.get(1))
            .map_or(false, |m| m.as_str() == section)
    }
}

// ----

if section.is(&line, section_name) {
    line.clear();
    continue;
}

The original code works as-is when NLL is enabled:

#![feature(nll)]

extern crate regex; // 1.0.1

use regex::Regex;

fn main() {
    let section_header_pattern = Regex::new(".").unwrap();
    let section_name = "";
    let mut line = String::new();
    let mut is_in_right_section = false;

    loop {
        if let Some(m) = section_header_pattern
            .captures(&line)
            .and_then(|c| c.get(1))
        {
            is_in_right_section = m.as_str().eq(section_name);
            line.clear();
            continue;
        }

        return; // I don't really want to loop
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366