How to deal with lifetime in closure?

I have following code:

impl StringExt for String {
    fn replacement(&self) -> Self {
        let replaced = NEWLINE_RE.replace_all(self, "\n");
        let replaced = QUOTES_RE.replace_all(&replaced, "");
        let replaced = LINKS_RE.replace_all(&replaced, |caps: &Captures| {
            let caps_str: Vec<_> = caps
                .iter()
                .flat_map(|c| c.map(|c| c.as_str()))
                .collect();
            caps_str.last().unwrap()
            //format!("{}", caps_str.last().unwrap())
        });
        replaced.into()
    }
}

When I try to compile I'm getting following error about lifetimes:

error: lifetime may not live long enough
  --> src/main.rs:72:13
   |
67 |         let replaced = LINKS_RE.replace_all(&replaced, |caps: &Captures| {
   |                                                         ----           - return type of closure is &'2 &str
   |                                                         |
   |                                                         has type `&regex::Captures<'1>`
...
72 |             caps_str.last().unwrap()
   |             ^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`

error[E0515]: cannot return value referencing local variable `caps_str`
  --> src/main.rs:72:13
   |
72 |             caps_str.last().unwrap()
   |             --------^^^^^^^^^^^^^^^^
   |             |
   |             returns a value referencing data owned by the current function
   |             `caps_str` is borrowed here

I can easily workaround it by using format, but of course I would like to know how to this proper. Using caps_str.last().cloned().unwrap() does not work either :confused:

Thanks!

Clone of a reference is still a reference. You need to return a String, so value wouldn't be lost when Vec<…> would be dropped. So… call to_owned there. Or maybe even into(), even that's a bit more cryptic.

1 Like

Why are you collecting when you only need the last one? Try this:

        let replaced = LINKS_RE.replace_all(&replaced, |caps: &Captures| {
            caps.iter()
                .filter_map(|c| c.map(|c| c.as_str()))
                .last()
                .unwrap()
                .to_owned()
        });

The replace_all closure can't return any borrows you got out of the Captures due to how the Replacer trait is implemented for closures.

impl<F, T> Replacer for F
where
    F: for<'outer, 'inner> FnMut(&'outer Captures<'inner>) -> T,
    T: AsRef<str>,

(It's possible to write a |caps: &Captures<'inner>| -> &'inner str, but in the above implementation, it's impossible for T to contain 'inner (or 'outer). It must be the same type for all the input lifetimes. That's why you need to return a String for replace_all.)

2 Likes

There are 2 issues at play here:

  1. caps_str.last().unwrap() returns &'_ &'haystack str, and you want to return &'haystack str from the closure, so you need an additional dereference: *caps_str.last().unwrap()
  2. as @quinedot pointed out, the impl Replacer for FnMut is formulated in such a way that the returned type T may not reference the "haystack" (because T does not depend on its lifetime).

If you want to avoid unnecessary allocations, you can create your own impl of Replacer that supports returning borrowed captures:

impl StringExt for String {
    fn replacement(&self) -> Self {
        let replaced = NEWLINE_RE.replace_all(self, "\n");
        let replaced = QUOTES_RE.replace_all(&replaced, "");
        let replaced = LINKS_RE.replace_all(
            &replaced,
            BorrowStrReplacer(|caps| {
                caps.iter()
                    .flat_map(|c| c.map(|c| c.as_str()))
                    .last()
                    .unwrap()
            }),
        );
        replaced.into()
    }
}

struct BorrowStrReplacer<F>(F)
where
    F: for<'haystack> FnMut(&Captures<'haystack>) -> &'haystack str;

impl<F> Replacer for BorrowStrReplacer<F>
where
    F: for<'haystack> FnMut(&Captures<'haystack>) -> &'haystack str,
{
    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
        dst.push_str((self.0)(caps).as_ref());
    }
}
3 Likes