Search code examples
rustclosureslifetimeborrow-checkercombinators

Value getting dropped too early inside closure and combinator while borrow exists


I'm facing a problem with a value being dropped while it is still borrowed inside an Option, in a closure, but I'm having a hard time grasping exactly what's going on. To illustrate, here is a working example of what I'm actually trying to achieve:

fn foo() -> Option<String> {
    let hd = match std::env::home_dir() {                                       
        Some(d) => d,                                                           
        None => return None,                                                    
    };                                                                          
    let fi = match hd.file_name() {                                             
        Some(f) => f,                                                           
        None => return None,                                                    
    };                                                                          
    let st = match fi.to_str() {                                                
        Some(s) => s,                                                           
        None => return None,                                                    
    };                                                                          
    Some(String::from(st))  
}

The return value is the base name of the current user's home directory inside Option<String>.

I thought I'd try refactoring this with combinators to get rid of the lines None => return None,.

std::env::home_dir()                                                        
    .and_then(|d| d.file_name())                                            
    .and_then(|f| f.to_str())                                               
    .map(String::from)

But rustc detects a reference that outlives its value.

error: `d` does not live long enough
  --> src/main.rs:33:35
   |
33 |         .and_then(|d| d.file_name())
   |                       -           ^ `d` dropped here while still borrowed
   |                       |
   |                       borrow occurs here
34 |         .and_then(|f| f.to_str())
35 |         .map(String::from)
   |                          - borrowed value needs to live until here

I think this is because the reference in Option<&OsStr> is outliving the value of type PathBuf. However I'm still having a hard time figuring out how to approach this without having the value go out of scope too soon.

To further illustrate what I'm trying to achieve, here is a similar example with a type that implements the Copy trait.

let x = 42u16.checked_add(1234)                                             
    .and_then(|i| i.checked_add(5678))                                      
    .and_then(|i| i.checked_sub(90))                                        
    .map(|i| i.to_string());                                                
println!("{:?}", x); // Some("6864")

So I'm definitely overlooking a few things related to ownership in the prior example. Is this possible with Option<PathBuf>?


Solution

  • You're right that you're consuming the PathBuf returned from home_dir() but still trying to use references.

    I would keep it in a variable, and work from there:

    fn foo() -> Option<String> {
        let path = std::env::home_dir();
        path.as_ref()
            .and_then(|d| d.file_name())
            .and_then(|f| f.to_str())
            .map(String::from)
    
    }
    

    (Playground)

    The call to path.as_ref() makes an Option<&PathBuf> as the starting point for the chain of and_then, without consuming the original owned PathBuf which is needed at least until String::from.