当用 catch_unwind 处理恐慌时,为什么不能将 Option.expect() 消息向下转换为 &'static str?

Why can't the Option.expect() message be downcast as a &'static str when a panic is handled with catch_unwind?

我有以下代码:

use std::thread;
use std::panic;

pub fn main(){
    thread::spawn(move || {
        panic::catch_unwind(|| {
            // panic!("Oh no! A horrible error.");
            let s: Option<u32> = None;
            s.expect("Nothing was there!");
        })
    })
    .join()
    .and_then(|result| {
        match result {
            Ok(ref val) => {
                println!("No problems. Result was: {:?}", val);
            }
            Err(ref err) => {
                if let Some(err) = err.downcast_ref::<&'static str>() {
                    println!("Error: {}", err);
                } else {
                    println!("Unknown error type: {:?}", err);
                }
            }
        }
        result
    });
}

当我直接触发 panic! 时(通过取消注释上面代码中的行),然后我得到一个包含我的错误消息的输出:

Error: Oh no! A horrible error.

但是,如果我像上面那样使用Option::expect(&str),那么消息就不能向下转换为&'static str,所以我无法得到错误消息:

Unknown error type: Any

如何获取错误消息,以及在一般情况下如何找到要向下转换为的正确类型?

Option::expect 期望消息为 &str,即具有任意生命周期的字符串切片。您不能将 &str 强制转换为 &'static str,因为字符串切片可能引用可以随时释放的 StringBox<str> 的内部。如果您要保留 &'static str 的副本,您将能够在 StringBox<str> 被删除后使用它,这将是未定义的行为。

一个重要的细节是 Any 特征不能保存任何生命周期信息(因此 'static 绑定),因为 Rust 中的生命周期在编译时被擦除。编译器使用生命周期来验证您的程序,但程序无法在运行时区分 &'a str&'b str&'static str

[...] how would I find the correct type to downcast to in the general case?

不幸的是,这并不容易。 Any 有一个方法(自 Rust 1.15.1 起不稳定)名为 get_type_id that lets you obtain the TypeId of the concrete object referred to by the Any. That still doesn't tell you explicitly what type that is, as you still have to figure out which type this TypeId belongs to. You would have to get the TypeId of many types (using TypeId::of) 并查看它是否与您从 Any 获得的方法匹配,但您可以使用 downcast_ref.

在这个例子中,AnyString。也许 Option::expect 最终可以被专门化,如果它的生命周期是 'static 并且只分配一个 String 如果它不是 'static.

正如 Francis 所说,您通常无法发现并转换为 panic 的类型。然而,话虽如此,恐慌有以下规则:

  • 如果您 panic! 使用单个参数,恐慌将具有该类型。通常这是 &'static str.
  • 如果您 panic! 使用多个参数,这些参数将被视为 format! 个参数并用于创建 String 个参数。

这些规则记录在 panic 文档中:https://doc.rust-lang.org/std/panic/fn.catch_unwind.html

考虑到这些规则,我们可以编写一个函数,在任何有消息可供提取的情况下从恐慌中提取消息,这在实践中大部分时间都有效,因为大部分时间消息是 &'static strString:

pub fn get_panic_message(panic: &Box<dyn Any + Send>) -> Option<&str> {
    panic
        // Try to convert it to a String, then turn that into a str
        .downcast_ref::<String>()
        .map(String::as_str)
        // If that fails, try to turn it into a &'static str
        .or_else(|| panic.downcast_ref::<&'static str>().map(Deref::deref))
}

我在不久前写的断言库中使用了这个函数;你可以在相关的 test suite.

中看到它的一些使用示例