How to resolve “error[E0515]: cannot return value referencing temporary value” without owned value?

Hi, folks!

In this code:

fn path_to_str(path: &PathBuf) -> &str {
    if let Some(s) = path.to_str() {
        s
    } else {
        ""
    }
}

fn join_path<'a>(path: &PathBuf, sufix: &str) -> &'a str {
    let full_pattern = &path.join(sufix);
    path_to_str(full_pattern)
}

I had the compilation result:

error[E0515]: cannot return value referencing temporary value --> src/main.rs:19:5

19 |     path_to_str(full_pattern)
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

Here is the playground:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=172f31549838105b8e98e225bbc206fa

This is the “rustc explain” return: Consider returning an owned value instead

It works with to_owned and changing the result function to String. But the documentation says that to_owned "Creates owned data from borrowed data, usually by cloning". I want to avoid cloning operations always when possible because I suppose cloning causes overhead processing.

I know the ownership rule that says "When the owner goes out of scope, the value will be dropped." I thought that using lifetime <'a> in function join_path I am telling the compiler to use a lifetime of the caller scope.
I found some article saying to use two lifetimes to resolve an issue with error[E0515] without cloning: https://dev.to/takaakifuruse/rust-lifetimes-a-high-wall-for-rust-newbies-3ap
But I tried a lot of combinations exploring two lifetimes with no success.

Question: is there a way to resolve this compilation issue without cloning?

Thanks.

path.join allocates a new PathBuf (full_pattern), and you're trying to return a reference to the data stored within that buffer. However, that full_pattern goes out of scope at the end of join_path, deallocating the data while a reference to it still exists. If the compiler allowed this, it'd immediately cause a use-after-free/null pointer error as soon as you tried to use the returned &str.

So your code as currently structured cannot work, regardless of what you do with lifetimes. You'll need to restructure it so that the return value of path_to_str doesn't outlive the joined PathBuf. The easiest way to do this would be to just inline the code from join_path:

use std::path::PathBuf;

fn main() {
    let root_path = PathBuf::from(".");
    let full_pattern = root_path.join("/sub");
    let new_path = path_to_str(&full_pattern);
    println!("new_path {}", new_path);
}

fn path_to_str(path: &PathBuf) -> &str {
    if let Some(s) = path.to_str() {
        s
    } else {
        ""
    }
}

I'm very grateful for your response. Thank you!

Cloning may indeed introduce overhead, but is it enough that you will notice? In my experience, most of the time you won't. I freely use clone when it's the easiest way to make progress on my project and have yet to see it have a major impact on the overall performance of my program. If it did show up, then I would spend time finding a workaround. My time implementing a new feature is far more valuable than spending time to save a few ms here and there.

On the other hand, this kind of tuning is a lot of fun and a good learning exercise. I have to admit that I have fewer clone operations in my code because of what I have learned from reading posts on this forum.

Great point of view. I'll consider this. Thank you for your response.