Ценность слишком рано падает внутри закрытия и комбинатора, пока существует заимствование


Я сталкиваюсь с проблемой, когда значение отбрасывается, пока оно все еще заимствовано внутри Option, в закрытии, но мне трудно понять, что именно происходит. Чтобы проиллюстрировать, вот рабочий пример того, чего я на самом деле пытаюсь достичь:

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))  
}

Возвращаемое значение-это базовое имя домашнего каталога текущего пользователя внутри Option<String>.

Я подумал, что попробую рефакторинговать это с помощью комбинаторов, чтобы избавиться от линий None => return None,.
std::env::home_dir()                                                        
    .and_then(|d| d.file_name())                                            
    .and_then(|f| f.to_str())                                               
    .map(String::from)

Но rustc обнаруживает a ссылка, которая переживает свою ценность.

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

Я думаю, что это потому, что ссылка в Option<&OsStr> переживает значение типа PathBuf. Однако мне все еще трудно понять, как подойти к этому без того, чтобы ценность слишком быстро вышла за рамки.

Чтобы еще больше проиллюстрировать то, чего я пытаюсь достичь, приведу аналогичный пример с типом, реализующим свойство копирования.
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")

Так что я определенно упускаю из виду несколько вещей, связанных с собственностью в предыдущий пример. Возможно ли это с Option<PathBuf>?

2 4

2 ответа:

Вы правы, что потребляете PathBuf, возвращенные из home_dir(), но все еще пытаетесь использовать ссылки.

Я бы сохранил его в переменной и работал оттуда:

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)

}

(игровая площадка )

Вызов path.as_ref() делает Option<&PathBuf> в качестве отправной точки для цепочки and_then, не потребляя исходную принадлежащую PathBuf, которая необходима по крайней мере до String::from.

Расширяя ответ Криса: вы также можете исправить проблему, вложив цепочку, начинающуюся со второго and_then в замыкание, переданное первому and_then. Это работает, потому что он сохраняет d (которому принадлежит PathBuf) живым, пока заимствования на нем не будут освобождены.

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