Panic 宏一致性

总结

  • panic!(..) 现在始终使用 format_args!(..),就像 println!() 一样。
  • 不再接受 panic!("{"),除非将 { 转义为 {{
  • 如果 x 不是字符串字面量,则不再接受 panic!(x)
    • 使用 std::panic::panic_any(x) 以非字符串有效负载引发 panic。
    • 或者使用 panic!("{}", x) 来使用 xDisplay 实现。
  • 这同样适用于 assert!(expr, ..)

详情

panic!() 宏是 Rust 中最著名的宏之一。但是,由于向后兼容性的原因,它有一些微妙的意外,我们无法轻易更改。

// Rust 2018
panic!("{}", 1); // Ok, panics with the message "1"
panic!("{}"); // Ok, panics with the message "{}"

仅当使用多个参数调用 panic!() 宏时,它才会使用字符串格式化。当使用单个参数调用时,它甚至不会查看该参数。

// Rust 2018
let a = "{";
println!(a); // Error: First argument must be a format string literal
panic!(a); // Ok: The panic macro doesn't care

它甚至接受非字符串,例如 panic!(123),这并不常见,而且很少有用,因为它会产生令人惊讶的无用消息:panicked at 'Box<Any>'

一旦隐式格式参数稳定下来,这将尤其成为一个问题。该功能将使 println!("hello {name}") 成为 println!("hello {}", name) 的简写。但是,panic!("hello {name}") 将无法按预期工作,因为 panic!() 不会将单个参数处理为格式字符串。

为了避免这种混乱的情况,Rust 2021 提供了一个更加一致的 panic!() 宏。新的 panic!() 宏将不再接受任意表达式作为唯一参数。与 println!() 一样,它将始终将第一个参数处理为格式字符串。由于 panic!() 将不再接受任意有效负载,因此 panic_any() 将是以非格式字符串引发 panic 的唯一方法。

// Rust 2021
panic!("{}", 1); // Ok, panics with the message "1"
panic!("{}"); // Error, missing argument
panic!(a); // Error, must be a string literal

此外,core::panic!()std::panic!() 在 Rust 2021 中将是相同的。目前,两者之间存在一些历史差异,在启用或禁用 #![no_std] 时可能会注意到这些差异。

迁移

每当调用 panic 使用了一些在 Rust 2021 中会出错的已弃用行为时,都会触发 non_fmt_panics lint。自 1.50 版本以来,non_fmt_panics lint 在所有版本中默认情况下都已成为警告(在以后的版本中进行了一些增强)。如果您的代码已经没有警告,那么它应该已经为 Rust 2021 做好了准备!

您可以通过运行以下命令自动将代码迁移到与 Rust 2021 版本兼容,或确保代码已经兼容:

cargo fix --edition

如果您选择或需要手动迁移,则需要更新所有 panic 调用,以使用与 println 相同的格式,或使用 std::panic::panic_any 以非字符串数据引发 panic。

例如,对于 panic!(MyStruct),您需要转换为使用 std::panic::panic_any(请注意,这是一个函数,而不是宏):std::panic::panic_any(MyStruct)