Rust 元組匹配技巧
我喜歡 Rust 的一個原因是它無處不在的模式匹配。
在我目前的項目 (一個多人遊戲服務器) 中,我所面臨的一個常見情況是:
1,運行多個函數,返回類型都是 Result<T,String>
2,如果他們都成功了,那就繼續執行
3,如果其中任何一個失敗,則使用一個錯誤執行一個錯誤場景。
例如,如果一個玩家施放了火球,我可能需要計算火球的大小、傷害和速度——三個獨立的計算。如果其中任何一個失敗了,就會出現問題,火球就不能施放了。
這也是編程中的一個普遍問題:在同一時間有多個表達式分支。
我最初的解決方案是做一堆嵌套檢查:
1if let Ok(response1) = func1() {
2 if let Ok(response2) = func2() {
3 if let Ok(response3) = func3() {
4 handleResponse(response1, response2, response3)
5 } else if let Err(e) {
6 handleError(e)
7 }
8 } else if let Err(e) {
9 handleError(e)
10 }
11} else if let Err(e) {
12 handleError(e)
13}
這是…… 混亂!至少可以這麼說。很難使其併發,儘管在某些情況下這可能是首選 (在 func1 完成之前不能執行 func2)。但這裏的業務邏輯本質上是,“計算 func1,檢查是否有錯誤,如果有,就處理它。計算 func2,檢查是否有錯誤…”,這很難讀懂。
所以我試着用元組來簡化它。我學到了一個很酷的技巧:
1match (func1(), func2(), func3()) {
2 (Ok(r1), Ok(r2), Ok(r3)) => handleResponse(r1, r2, r3),
3 (Err(e), _, _) |
4 (_, Err(e), _) |
5 (_, _, Err(e) => handleError(e)
6}
注意,在三個不同的匹配情況下,e 來自三個不同的變量。但只要每種情況的 Err 類型相同,Rust 就允許我們在單個表達式中使用不同的變量。
如果我們不關心錯誤響應,當然我們可以進一步簡化:
1match (func1(), func2(), func3()) {
2 (Ok(r1), Ok(r2), Ok(r3)) => handleResponse(r1, r2, r3),
3 _ => handleError()
4}
這是一個很酷的技巧,但對我來說,這是一個範式的突破。對於非相關函數,我們可以在一個元組中聚合它們的結果,然後進行模式匹配。它允許我從業務邏輯而不是代碼的角度進行思考。
使用?使用內聯閉包的語法,我們可以進一步簡化:
1match (|| Ok(func1()?, func2()?, func3()?))() {
2 Ok((r1, r2, r3)) => handleResponse(r1, r2, r3),
3 Err(e) => handleError(e),
4}
雖然閉包語法有點不清楚,但這種風格的一個優點是,如果第一個函數返回一個錯誤,第二個和第三個函數就不會被執行。我們也可以使用函數代替閉包來增加清晰度:
1fn calc() -> Result<(R1, R2, R3), E> {
2 let r1 = func1()?;
3 let r2 = func2()?;
4 let r3 = func3()?;
5 Ok((r1, r2, r3))
6}
7
8match calc() {
9 Ok(r) => handle_response(r),
10 Err(e) => handle_error(e),
11}
還有一個方法是使用 and_then,但這是最不清晰的方法:
1let result = func1()
2 .and_then(|r1| func2().map(|r2| (r1, r2)))
3 .and_then(|(r1, r2)| func3().map(|r3| (r1, r2, r3)));
4
5match result {
6 Ok(r) => handle_response(r),
7 Err(e) => handle_error(e),
8}
本文翻譯自:
https://nathanael-morris-bennett.medium.com/rust-tuple-pattern-matching-trick-c0f6bcdb4460
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/lfyTe4_Xq9Up8H4SjFhlJQ