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:

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: Rust lifetimes, a high wall for Rust newbies - DEV Community 👩‍💻👨‍💻
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.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.