Rust中用元組模式匹配替代if-else

banq發表於2022-09-19

在當前專案(多人遊戲伺服器)中遇到的常見情況如下:
  • 1)執行多個返回 Result<T, String> 的函式。
  • 2a) 如果他們都成功了,繼續走快樂的道路
  • 2b) 如果他們中的任何一個失敗了,用他們的第一個錯誤執行一個錯誤場景。


這也是程式設計中的一個普遍問題:同時從多個表示式分支。
最初的解決方案是做一堆巢狀檢查:

if let Ok(response1) = func1() { 
  if let Ok(response2) = func2() { 
    if let Ok(response3) = func3() { 
      handleResponse(response1, response2, response3) 
   } else if let Err(e) { 
      handleError(e) 
    } 
  } else if let Err(e) { 
    handleError(e) 
  } 
} else if let Err(e) { 
  handleError(e) 
}


嘗試透過使用元組來簡化它:

match (func1(), func2(), func3()) {
    (Ok(r1), Ok(r2), Ok(r3)) => handleResponse(r1, r2, r3),
    (Err(e), _, _) | 
    (_, Err(e), _) |
    (_, _, Err(e) => handleError(e)
}

注意e來自 3 個不同的變數,在三種不同的匹配情況下。
但是隻要每個案例之間的 Err 型別相同,Rust 就允許我們在單個表示式中使用不同的變數。

如果我們不關心錯誤響應,當然我們可以進一步簡化:

match (func1(), func2(), func3()) {
  (Ok(r1), Ok(r2), Ok(r3)) => handleResponse(r1, r2, r3),
  _ => handleError()
}


Rustaceans為這種模式提供了一些很酷的變體。使用? 內聯閉包的語法,我們可以進一步簡化:

match (|| Ok(func1()?, func2()?, func3()?))() {
    Ok((r1, r2, r3)) => handleResponse(r1, r2, r3),     
    Err(e) => handleError(e),
}


雖然閉包語法有點不清楚,但這種風格的一個優點是,如果第一個函式返回一個錯誤變數,第二個和第三個函式甚至都不會被執行。我們還可以使用離散函式而不是閉包來增加清晰度:

fn calc() -> Result<(R1, R2, R3), E> { 
    let r1 = func1()?; 
    let r2 = func2()?; 
    let r3 = func3()?; 
    Ok((r1, r2, r3))
}
match calc() { 
    Ok(r) => handle_response(r), 
    Err(e) => handle_error(e), 
}


另一個變體是and_then 

let result = func1()
    .and_then(|r1| func2().map(|r2| (r1, r2)))
    .and_then(|(r1, r2)| func3().map(|r3| (r1, r2, r3)));
match result { 
    Ok(r) => handle_response(r), 
    Err(e) => handle_error(e), 
}


更多ifelse 
 

相關文章