"context" 和 "with_context" 到底有什么区别?

What is the difference between "context" and "with_context" in anyhow?

无论如何,这是 Context:

的文档
/// Wrap the error value with additional context.
fn context<C>(self, context: C) -> Result<T, Error>
where
    C: Display + Send + Sync + 'static; 
/// Wrap the error value with additional context that is evaluated lazily
/// only once an error does occur.
fn with_context<C, F>(self, f: F) -> Result<T, Error>
where
    C: Display + Send + Sync + 'static,
    F: FnOnce() -> C;

实际上,不同之处在于 with_context 需要一个闭包,如 README:

所示
use anyhow::{Context, Result};

fn main() -> Result<()> {
    // ...
    it.detach().context("Failed to detach the important thing")?;

    let content = std::fs::read(path)
        .with_context(|| format!("Failed to read instrs from {}", path))?;
    // ...
}

但看起来我可以用 context 替换 with_context 方法,通过删除 || 来摆脱闭包,并且程序的行为不会改变。

这两种方法在本质上有什么区别?

正如 anyhow::Context::with_context 的文档所述:

Wrap the error value with additional context that is evaluated lazily only once an error does occur.

如果传递给 context 的内容可能在计算上很昂贵,最好使用 with_context,因为传递的闭包仅在调用 with_context 时计算。这被称为以 lazy 而不是 eager 方式进行评估。

标准库中存在类似的行为,例如:

提供给 with_context 的闭包是延迟求值的,你使用 with_context 而不是 context 的原因与你选择延迟求值任何东西的原因相同:它很少发生,而且计算起来很昂贵。一旦满足这些条件,那么 with_context 就比 context 更可取。注释伪示例:

fn calculate_expensive_context() -> Result<()> {
    // really expensive
    std::thread::sleep(std::time::Duration::from_secs(1));
    todo!()
}

// eagerly evaluated expensive context
// this function ALWAYS takes 1+ seconds to execute
// consistently terrible performance
fn failable_operation_eager_context(some_struct: Struct) -> Result<()> {
    some_struct
        .some_failable_action()
        .context(calculate_expensive_context())
}

// lazily evaluated expensive context
// function returns instantly, only takes 1+ seconds on failure
// great performance for average case, only terrible performance on error cases
fn failable_operation_lazy_context(some_struct: Struct) -> Result<()> {
    some_struct
        .some_failable_action()
        .with_context(|| calculate_expensive_context())
}