Borrow checker and string replaces

I'm trying to parse a non-structured text by modifying each line in a number of steps. At the moment, I have this (MWE) (playground):

use regex::Regex;


fn main () {
    let text = String::from("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec quis feugiat massa. Fusce id est libero. Aliquam fringilla velit eget sollicitudin porta. Vivamus fringilla aliquam vestibulum. In hac habitasse platea dictumst. Etiam rhoncus convallis laoreet. Cras porta nisi a elit dignissim commodo. Phasellus massa urna, dictum molestie cursus vel, porttitor quis erat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec non dapibus dolor. Ut maximus tincidunt ligula cursus tristique.

    Curabitur consectetur, ex ac tristique ultricies, neque dolor porta metus, sed dapibus purus lacus in lacus. Sed vel libero nulla. Donec non aliquet nibh. Curabitur laoreet libero lectus, nec finibus est pulvinar et. Phasellus vehicula, nulla eget molestie cursus, sapien nibh ornare mauris, molestie dapibus tellus magna in lectus. Mauris porta a elit id fringilla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum varius eros augue, ullamcorper porta tortor aliquet at. Proin sodales commodo molestie. Pellentesque id leo eu magna sollicitudin aliquam nec et orci. Fusce sollicitudin, nulla non sollicitudin tincidunt, nulla lacus consequat quam, a tempor nisi ex ut erat. Maecenas venenatis tincidunt fermentum. Integer hendrerit felis ullamcorper, dapibus nisl in, lacinia purus. Etiam sollicitudin rutrum mattis.

    Fusce sed lacinia ipsum, ut tincidunt neque. Aliquam nunc magna, volutpat vitae velit in, volutpat accumsan odio. Fusce nec quam egestas, feugiat metus ac, vehicula diam. Curabitur facilisis varius magna, at lobortis eros luctus nec. Vestibulum turpis tellus, egestas vitae tincidunt nec, egestas eu dolor. Nulla sit amet metus eu mauris dapibus tristique. Duis pretium tempus gravida. Donec imperdiet pulvinar nibh, sit amet volutpat ipsum viverra a.

    Morbi imperdiet, purus euismod molestie dictum, orci lorem dictum justo, nec tincidunt velit purus fringilla sem. Nulla justo diam, porttitor sit amet purus in, dapibus pellentesque dui. Nunc hendrerit, erat ut lacinia viverra, tellus sapien tempus enim, id vulputate dui augue et mauris. Duis pellentesque dapibus turpis, in tempus lectus fringilla tincidunt. Aliquam erat volutpat. Duis a tempor erat, vel iaculis nunc. Morbi sollicitudin augue ut nunc blandit, vitae congue diam sagittis. Sed at dolor feugiat, blandit lorem quis, ullamcorper libero. Phasellus facilisis dolor ut accumsan dictum. Nunc convallis magna vitae turpis eleifend, non faucibus nisi semper. Vestibulum eu posuere risus, ac suscipit tellus.

    Vestibulum ac tellus eu magna convallis fringilla sit amet vitae urna. Donec commodo luctus aliquet. Aliquam finibus in magna non commodo. Curabitur placerat justo in ex viverra rutrum id eget augue. Nullam ut lacinia quam, eu fringilla neque. Curabitur imperdiet cursus leo, ut aliquet enim cursus nec. Cras id leo ut ipsum blandit ultrices. Curabitur posuere dapibus magna, ut accumsan nibh. Ut ac consequat risus. Proin sed scelerisque ante. Aenean nec mi at nunc dictum convallis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent euismod cursus quam ut tristique.");


 let _output: Vec<String> = problem(text);

}

fn problem(text: String) -> Vec<String>{
    // purpose: parse and structure the input text (not yet complete)
    let mut done: Vec<String> = vec![];

    // the real regex is quite a bit longer, symplified for clarity
    let regex = Regex::new("(a|b)*").unwrap();

    // purpose: parse text one line at a time, this loop will be expanded with
    // more replacements. It seemed like a good idea to modify the line in place
    // to avoid copying it?
    for mut line in text.lines() {
        line = line.trim();
        line = &regex
            .replace_all(
                line,
                r"[replacement]",
            );

        line = &line.replace("a", "b");

        println!("{:#?}\n", line);
        done.push(line.to_string());
    }

    done
}

However, rustc says I need to use let with each replacement.This seems strange, as I don't want to make a new variable each time but rather change the same variable "in place". The error is this:

   Compiling playground v0.0.1 (/playground)
error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:32:17
   |
32 |           line = &regex
   |  _________________^
33 | |             .replace_all(
34 | |                 line,
35 | |                 r"[replacement]",
36 | |             );
   | |             ^- temporary value is freed at the end of this statement
   | |_____________|
   |               creates a temporary which is freed while still in use
37 | 
38 |           line = &line.replace("a", "b");
   |                   ---- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:38:17
   |
38 |         line = &line.replace("a", "b");
   |                 ^^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
   |                 |
   |                 creates a temporary which is freed while still in use
39 | 
40 |         println!("{:#?}\n", line);
   |                             ---- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0716`.
error: could not compile `playground`.

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

Prepending a let solves the error, but I don't understand why rustc asks this here, nor whether using a let each time (as the number of substitions will increase) is a good idea? Could someone point me in the right direction to understanding this?

You're getting an owned value (the result of regex.replace_all) and then immediately taking a reference to it, but since it isn't assigned to anything, the scope it's in is just the statement that creates it, so it gets dropped before you can get a reference to it. Essentially, to have a reference to something, that thing has to live longer than the reference, and you make it live longer by keeping it in scope.

Using a let each time is perfectly fine - you aren't really modifying the line itself; for the most part you're just reusing the name for some slicing (for trim and, potentially, replace_all) - additionally, each of the three operations you're doing return types of different levels of ownership, so you'll have to have some of them keep on bound anyway (as a note, you're already guaranteed doing a copy when you do replace.)

let line = line.trim();
// line: &str - borrowed
let line = regex.replace_all(line, r"[replacement]");
// line: Cow<str> - potentially owned
let line = line.replace("a", "b");
// line: String - definitely owned

done.push(line); // no need to call `to_string`
1 Like

I see, thanks a lot!