DiamondDust
DiamondDust

Reputation: 21

Return string how to fix cannot return value referencing temporary value

I want to filter the msg in debug_args function before returning:

pub trait MyLog {
    fn debug_args(&self) -> &dyn std::fmt::Debug;
}

pub struct TracingMyLog<'a> {
    record: &'a tracing::Event<'a>,
    fields: Map<String, Value>,
    message: Option<String>,
}

impl<'a> MyLog for TracingMyLog<'a> {
    fn debug_args(&self) -> &dyn std::fmt::Debug {
        if let Some(msg) = self.message.as_ref() {
            msg
        } else {
            &""
        }
    }
}

I want to use Regex on msg but I get this error: cannot return value referencing temporary value returns a value referencing data owned by the current function.

fn regex_msg(str: &String) -> String {
    let re = Regex::new("sig: \"0x[a-fA-F0-9]+\"").unwrap();
    re.replace_all(&str, "sig: \"***filtered***\"")
        .to_string()
}

    fn debug_args(&self) -> &dyn std::fmt::Debug {
        if let Some(msg) = self.message.as_ref() {
            &regex_msg(msg)
        } else {
            &""
        }
    }

I understand that this is trying to return a reference to a value that is local to the function and once the function returns, its local values are dropped and any references to them would dangle.

I also tried cloning the msg string in a new function & returning a reference to it which is also not allowed by the compiler.

Finally, I think a workaround exists with Cow<str> but I don't wanna use it.

Q. How can I achieve the filtering operation on the above msg without changing the function signature if possible?

Upvotes: 2

Views: 954

Answers (2)

Rasmus Kaj
Rasmus Kaj

Reputation: 4360

A returned &str always has a lifetime. So if you want to return a &str that references data you created in the function, you have, in general, two possibilities:

  1. Store the data in the self object, and return a reference with the lifetime of &self.
  2. Leak the data and return a &'static str (in general, don't do this! It may be ok in setup code where the lifetime of the data really should be the lifetime of the program, but that don't seem to be the case in the question).

Maybe you could apply the regex when storing message (assuming self.message is a private field)?

Or, as a different variation of the theme, you could store message not as an Option<String> but as an Option<MySecretString>, return a reference to that directly and put the regex handling in the Debug impl of MySecretString. I think this is what I would do.

Upvotes: 0

JMAA
JMAA

Reputation: 2067

Personally, this is how I'd approach this:

If you really cannot change the signature of debug_args, then I'd store the filtered message in a String member of your struct so you can return a reference to it. However, MyLog looks like your own trait, so I'd really advise you change that instead.

I would change MyLog to look like this:

pub trait MyLog<'a, T>
where
    T: std::fmt::Debug + 'a
{
    fn debug_args(&'a self) -> T;
}

This is much more idiomatic than returning a &dyn Trait (which has its place, but isn't necessary here from what I can see). This is basically saying "you can implement this to return any type, so long as that type implements Debug and lives at least as long as the &self you borrow".

Then you can implement it like this:

impl<'a, 'b> MyLog<'a, Cow<'a, str>> for TracingMyLog<'b>
{
    fn debug_args(&self) -> Cow<str> {
        if let Some(msg) = self.message.as_ref() {
            regex_msg(msg)
        } else {
            "".into()
        }
    }
}

fn regex_msg(msg: &str) -> Cow<str> {
    let re = Regex::new("sig: \"0x[a-fA-F0-9]+\"").unwrap();
    re.replace_all(msg, "sig: \"***filtered***\"")
}

This is saying "logging this returns a Cow<str>: it might borrow a string from &self or it might not".

Additionally, I've fixed up the signature of regex_msg. First, you should always prefer &str over &String as a parameter (it's more flexible, more idiomatic, and very very rarely do you actually care whether the data is stored in a String or, for example, a literal). Second, it now returns Cow<str> just like Regex::replace_all, avoiding an extra allocation if no matches were found. Finally, please don't name a variable str: it will probably confuse readers of your code as it's a built-in type.

Playground link


A very slightly simpler version would be to instead do

pub trait MyLog<T>
where
    T: std::fmt::Debug
{
    fn debug_args(&self) -> T;
}

But now you couldn't return a Cow<'_, str> (since there's no link between the lifetime of &self and T), so you'd have to impl MyLog<String> and just return an allocated String. For the sake of a couple of lifetime annotations I think that avoiding the allocation is totally worth it.

Upvotes: 1

Related Questions