Dangling parameter deluxe - how to search for the origin?

Hi all,

I can not get my head around this error:

   --> src/collection.rs:223:32
    |
222 |                     if r.exists() && r.is_dir() && let Ok(c) = Self::open(&r.to_path_buf()) {
    |                                                                            --------------- temporary value created here
223 |                         return Ok(c);
    |                                ^^^^^ returns a value referencing data owned by the current function

I think I understood this explanation. I can not pass some reference from within some method to its caller for that's a dangling reference. Same is true for any value / parameter inside a method that gets passed on to other (inner) methods. That parameter still is local to the method an can not be returned.

I tried to get a reduced implementation of my own sources. The reduction looks like this and works well.

use std::path::PathBuf;
use std::path::Path;
struct Collection {
    path: PathBuf,
}
impl Collection {
    pub fn new(r: &Path) -> Result<Collection, String> {
        if r.exists() && r.is_dir() && let Ok(c) = Self::open(&r.to_path_buf()) {
            return Ok(c);
        }
        Ok(Collection { path: r.to_path_buf(), })
    }
    pub fn open(r: &PathBuf) -> Result<Collection, String> {
        Ok(Collection { path: r.to_path_buf(), })
    }
}
fn main() {
    let _ = Collection::new(Path::new("."));
}

I have no idea why my source (line 222 above) does not compile. I think it's the open-method of mine. Looking at that method - what am I searching for? What causes line 222 to fail? I'm using plenty of clone within my open to create owned data that will not dangle. What else can I check for?

If whatever is returned on line 223 hold a borrow ("reference data"), it must have a generic of some kind -- a type or a lifetime. Look for a way that that a lifetime in the input to open could flow into the output type. #[deny(elided_lifetimes_in_paths)] can help illuminate the signatures by erroring when lifetimes parameters are elided. Alternatively, replace the function body with todo!() temporarily, and recent Rust versions will give a warning about it.

In this playground I've modified your example so that it's a lifetime flowing into the return type, but it's also possible with generic types, like if your signature looks something like

pub fn open(r: F) -> Result<Collection<F>, String> {

then F could be a &'some_lifetime PathBuf or such.


Incidentally, I recommend "taking what you need" in your signatures. For example here:

    pub fn open(r: &PathBuf) -> Result<Collection, String> {
        Ok(Collection { path: r.to_path_buf(), })
    }

You need a PathBuf, so take r: PathBuf, not &PathBuf or &Path.

And in the case where you don't need ownership, take &Path and not &PathBuf. The former is more general and less indirect.

1 Like

Thank you very much. I had to take two steps:

  1. Applying "taking what you need" in my signatures (and implementing bodies accordingly) and

  2. adding a lifetime to new and open as well.

Ownership. One find day ...