1

I'm rather new to rust, and I'm struggling with the lifetime stuff rn. So basically I'm constructing a struct and I want to store a closure in that struct. But one of the captured variables does not live long enough.

struct HtmlOut<'a> {
  echo_escaped: &'a dyn Fn(&String)
}

// not really implemented yet
fn escape(s: &String) -> &String {
  return s;
}

impl<'a> HtmlOut<'a> {
  
  fn new(echo: &'a dyn Fn(&String)) -> HtmlOut<'a> {
    let echo_element_escaped_cls = |s: &String| { echo(escape(s)) };
    let echo_element_escaped: &'a dyn Fn(&String) = &echo_element_escaped_cls;
    return HtmlOut {
      echo_escaped: echo_element_escaped
    }
  }
}

I get two errors:

|s: &String| { echo(escape(s)) };
------------   ^^^^ borrowed value does not live long enough

and

&'a dyn Fn(&String) = &echo_element_escaped_cls;
-------------------   ^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
|
type annotation requires that `echo_element_escaped_cls` is borrowed for `'a`

My questions are:

  • why does echo not live long enough, when everything is annotated with the same lifetime specifier 'a?
  • how do I rewrite this code so that echo lives long enough?
John Smith
  • 2,282
  • 1
  • 14
  • 22

1 Answers1

0

Your code can't work as written simply because references have to refer to something. Rust doesn't have a GC, so you can't just create a value in a function, create a reference to it, and return the reference from the function. You need to own the value instead, which you do either by storing the value inside the struct, or in the case of dyn Trait trait objects, by boxing it:

struct HtmlOut {
    echo_escaped: Box<dyn Fn(&str)>,
}

(I also modified the signature to accept &str rather than &String which is more flexible and works as nicely, details here.)

The other problem is with your escape function - to escape a string, it has to be able to create the escaped string, not just return the reference to a string created by someone else. It needs to either accept &mut String to modify the string in-place, or return an owned String:

// not really implemented yet
fn escape(s: &str) -> String {
    s.to_string()
}

Finally, the constructor needs to accept echo as an owned value, so it can be moved into the closure:

impl HtmlOut {
    fn new(echo: Box<dyn Fn(&str)>) -> HtmlOut {
        let echo_element_escaped_cls = move |s: &str| echo(&escape(s));
        let echo_element_escaped = Box::new(echo_element_escaped_cls);
        return HtmlOut {
            echo_escaped: echo_element_escaped,
        };
    }
}

For maximum flexibility the constructor could even accept an arbitrary closure and do the boxing as its own implementation detail:

fn new<F: Fn(&str) + 'static>(echo: F) -> HtmlOut {
    let echo = Box::new(echo);
    let echo_element_escaped_cls = move |s: &str| echo(&escape(s));
    let echo_element_escaped = Box::new(echo_element_escaped_cls);
    return HtmlOut {
        echo_escaped: echo_element_escaped,
    };
}

Playground

user4815162342
  • 141,790
  • 18
  • 296
  • 355