2016-12-13 12 views
6

Mam ten kod.Czy istnieje sposób na powiązanie mapowania wyników i rozpakowywania?

if let Ok(file) = env::var("CONF") { 
    if let Ok(mut reader) = fs::File::open(&file) { 
     if let Ok(conf) = Json::from_reader(&mut reader) { 
      // do something with conf 
     } 
    } 
} 

Staram się, aby było mniej jak świąteczne drzewo świąteczne i myślałem o łańcuchowaniu. Zauważ, że każdy krok w tym łańcuchu daje kolejny Result, więc wyraźnie to nie zadziała (otrzymamy Result w Result).

let conf = env::var("CONF") 
    .map(fs::File::open) 
    .map(Json::from_reader); 

// do something with conf 

także moje typy błędów różnią się dla każdego kroku, co oznacza, że ​​nie można po prostu zastąpić .map z .and_then.

Myślę, że szukam czegoś podobnego do obietnic JavaScriptu. Oznacza to, że obietnica zwrócona z wewnątrz obietnicy odwija ​​wewnętrzną obietnicę. Podpis powinien prawdopodobnie wyglądać następująco:

impl<T, E> Result<T, E> { 
    fn map_unwrap<F, U, D>(&self, op: F) -> Result<U, D> 
     where F: FnOnce(T) -> Result<U, D> 
} 

Czy istnieje taki mechanizm w Rust? Czy istnieje inny sposób na pozbycie się mojego świątecznego drzewa na wakacje?

+4

W tym momencie powstał świetny artykuł: https://mgattozzi.com/posts/russian-dolls.html – squiguy

+0

Dzięki! Sprawdzam to! –

+0

Używam 'try!()' Makro – Stargateur

Odpowiedz

7

Czy istnieje taki mechanizm w Rust?

Tak - choć nie wszystkie w jednym ujęciu, jakie prezentujesz. Spójrzmy na podpis teoretyczne:

impl<T, E> Result<T, E> { 
    fn map_unwrap<F, U, D>(&self, op: F) -> Result<U, D> 
    where 
     F: FnOnce(T) -> Result<U, D>, 
    {} 
} 

To nie może działać - zakładamy, że możemy zacząć od wariantu Err - jak by to kod wiem jak przekonwertować z E do D? Ponadto, &self nie jest odpowiedni dla funkcji, które chcą konwertować typy; zwykle zajmują one self.

Istnieją dwa elementy, które trzeba będzie łączyć:

  1. Result::and_then

    impl<T, E> Result<T, E> { 
        fn and_then<U, F>(self, op: F) -> Result<U, E> 
        where 
         F: FnOnce(T) -> Result<U, E>, 
        {} 
    } 
    
  2. Result::map_err

    impl<T, E> Result<T, E> { 
        fn map_err<F, O>(self, op: O) -> Result<T, F> 
        where 
         O: FnOnce(E) -> F, 
        {} 
    } 
    

następnie będzie n eed typu, który może reprezentować oba typy błędów. Będę leniwi i używać Box<Error>

połączone razem, trzeba coś takiego:

use std::env; 
use std::fs::File; 
use std::error::Error; 

fn main() { 
    let conf = env::var("CONF") 
     .map_err(|e| Box::new(e) as Box<Error>) 
     .and_then(|f| File::open(f).map_err(|e| Box::new(e) as Box<Error>)); 
} 

Teraz każde wywołanie konwertuje wartość błędu do wspólnego typu, a wynik jest chainable z and_then. Przypuszczalnie twój prawdziwy kod tworzy typ błędu odpowiadający twojemu problemowi, a następnie użyjesz go w wywołaniu map_err.Ja bym implement From, to można mieć tylko:

let conf: Result<_, Box<Error>> = env::var("CONF") 
    .map_err(Into::into) 
    .and_then(|f| File::open(f).map_err(Into::into)); 
+0

'Into :: into' to miły akcent. Czy istnieje funkcja "bezpłatna", aby uzyskać ref? 'String' ->' & String'? –

+0

@MartinAlgesten Myślę, że szukasz ['AsRef :: as_ref'] (https://doc.rust-lang.org/std/convert/trait.AsRef.html), ale może nie działać w przypadku, gdy chcieć. Nie można odwzorować na przykład opcji "Opcja " na "Opcja <&str>", ponieważ odwoływałaby się ona do czegoś, co następnie zostałoby usunięte. Jest jednak 'Option :: as_ref'. – Shepmaster

4

Jeśli rzeczywiście chcesz zignorować wyniki jak robisz z if let można użyć makra tak:

macro_rules! iflet { 
    ([$p:pat = $e:expr] $($rest:tt)*) => { 
     if let $p = $e { 
      iflet!($($rest)*); 
     } 
    }; 
    ($b:block) => { 
     $b 
    }; 
} 


fn main() { 
    iflet!([Ok(file) = env::var("CONF")] 
      [Ok(mut reader) = File::open(&file)] 
      [Ok(conf) = Json::from_reader(&mut reader)] { 
     // do something with conf 
    }); 
} 

Playground (without Json part)

Makro pochodzi z an answer I made to a similar question on Options, ale działa z każdym if let. Chociaż z Result często chcesz używać w pewnym sensie modelu Err, zazwyczaj pochylam się w stronę the approach explained by Shepmaster lub ?/try!.

+0

Makro to z pewnością sposób. Dzięki! –