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?
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 {
""
}
}
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.