2015-07-01 13 views
7

Ich habe einige Code, der wie folgt aussieht:Gibt es eine nicht-chaotische Möglichkeit, die Ergebnisse von Funktionen zu verketten, die Optionswerte zurückgeben?

f(a).and_then(|b| { 
    g(b).and_then(|c| { 
     h(c).map(|d| { 
      do_something_with(a, b, c, d) 
     }) 
    }) 
}) 

Wo f, g und h Rückkehr Option Werte. Ich muss alle Zwischenwerte (a, b, c und d) in der do_something_with Berechnung verwenden. Die Vertiefung ist sehr tief. Gibt es einen besseren Weg, dies zu tun? Ideal wäre es etwa so aussehen (was natürlich nicht funktioniert):

try { 
    let b = f(a); 
    let c = g(b); 
    let d = h(c); 
    do_something_with(a, b, c, d) 
} rescue NonexistentValueException { 
    None 
} 
+0

tun, um alle Funktionen liefern die gleiche Option Typ? oder ändern sie den Typ? –

+1

, falls sie sind: hier ist ein Makro-freie Lösung: http://is.gd/eItCTh andere Lösungen erfordern Wert Generika oder variadische Generika oder ein Merkmal wie [FixedSizeArray] (https://doc.rust-lang.org /nightly/core/array/trait.FixedSizeArray.html) nur für Tupel –

Antwort

4

Die Rust-Standardbibliothek definiert einen try! Makro, das dieses Problem für Result löst. Das Makro sieht wie folgt aus:

macro_rules! try { 
    ($expr:expr) => (match $expr { 
     $crate::result::Result::Ok(val) => val, 
     $crate::result::Result::Err(err) => { 
      return $crate::result::Result::Err($crate::convert::From::from(err)) 
     } 
    }) 
} 

Was sie tut, ist, wenn das Argument Err ist, es von der Funktion gibt mit diesem Err Wert. Andernfalls wird der Wert Ok ausgewertet. Das Makro kann nur in einer Funktion verwendet werden, die Result zurückgibt, da es den Fehler zurückgibt, den es erfüllt.

Wir können ein ähnliches Makro für Option machen:

fn do_something(a: i32) -> Option<i32> { 
    let b = try_opt!(f(a)); 
    let c = try_opt!(g(b)); 
    let d = try_opt!(h(c)); 
    do_something_with(a, b, c, d) // wrap in Some(...) if this doesn't return an Option 
} 
5

Inspiriert von dem Konzept der try! für Ergebnis, wollen wir unser eigenes Makro wickeln:

macro_rules! try_opt { 
    ($expr:expr) => (match $expr { 
     ::std::option::Option::Some(val) => val, 
     ::std::option::Option::None => return None 
    }) 
} 

Sie dann dieses Makro wie folgt verwenden können In der frühen Rückkehr aus dem Rahmen, wenn sinkt die Monade None.

macro_rules! get(
    ($e:expr) => (match $e { Some(e) => e, None => return None }) 
); 

(Stolen aus this reddit thread)

Jetzt können Sie Ihren Code linear laufen:

fn blah() -> Option<...> { // ... is the return type of do_something_with() 
    let a = 123; 
    let b = get!(f(a)); 
    let c = get!(g(b)); 
    let d = get!(h(c)); 
    do_something_with(a, b, c, d) 
} 

(runnable gist)