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


在当前项目(多人游戏服务器)中遇到的常见情况如下:

  • 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