错误处理

有效的错误处理在应用中至关重要,可确保应用行为可预测。

Rinf 期望开发者仅将 Flutter 用于 UI 层,而将所有业务逻辑保留在 Rust 中。这种方式鼓励直接在 Rust 中处理错误和记录日志,无需跨越语言边界。[1]

以下是在实际应用中管理错误的一些推荐做法。

不要使用 Panicking

我们建议您不要编写 panicking 代码,因为 Rust 提供了惯用的 Result<T, E>。此外,Rust 无法在 Web 平台(wasm32-unknown-unknown)上捕获 panic,这可能导致调用方永远等待。

Rust
fn not_good() {
  let option = get_option();
  let value_a = option.unwrap(); // 此代码可能 panic
  let result = get_result();
  let value_b = result.expect("This code can panic");
}

fn good() -> Result<(), SomeError> {
  let option = get_option();
  let value_a = option.ok_or(SomeError)?;
  let result = get_result();
  let value_b = result?;
  Ok(())
}

正如 Rust 文档所述,大多数错误并不严重到 需要程序或任务完全停止。

灵活的错误类型

为了有效管理 Rust 错误,使用灵活的错误类型会很有帮助。

开发应用与创建库不同,因为应用可能遇到各种各样的错误情况。为每种可能的失败情形都声明一个不同的错误枚举变体可能会让人不堪重负,除非错误情况足够简单。

因此,建议使用单一且灵活的错误类型。您可以定义自己的类型,也可以使用 crates.io 上的 crate:

Rust
use anyhow::{Context, Result};

fn get_cluster_info() -> Result<ClusterMap> {
  // `anyhow::Error` 可以从任何错误类型创建。
  // 使用 `?` 操作符时,转换会自动发生。
  let config = std::fs::read_to_string("cluster.json")?;
  // 使用 `context` 方法,您可以用附加信息包装原始错误。
  let map: ClusterMap = serde_json::from_str(&config)
    .context("Failed to parse cluster configuration as JSON")?;
  Ok(map)
}

日志记录

您可能希望将错误记录到控制台或文件中。有几个 crate 可以帮助完成此过程:

使用集中化的 trait 来记录错误会很有帮助。通过调用一个通用的日志记录方法,您可以一致地处理传播的错误。Rust 会自动警告您未使用的 Result,从而更容易处理代码中的所有错误。

下面的 trait 演示了如何仅消费错误变体以进行日志记录。它的工作方式类似于 Result::ok 方法,但具有额外的日志记录功能。

Rust
use anyhow::Result;
use tracing::error;

pub trait ReportError<T> {
  fn report(self) -> Option<T>;
}

impl<T> ReportError<T> for Result<T> {
  fn report(self) -> Option<T> {
    match self {
      Ok(inner) => Some(inner),
      Err(err) => {
        error!("{:?}", err);
        None
      }
    }
  }
}
Rust
fn example_function() {
  // 从顶层函数报告错误。
  let empty_result: Result<()> = returns_empty_result();
  empty_result.report();

  // 在消费结果值之前报告错误。
  for _ in 0..5 {
    let number_result: Result<i32> = returns_number_result();
    let number = match number_result.report() {
      Some(inner) => inner,
      None => continue,
    };
    use_number(number);
  }
}