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 中会报错的已弃用行为时,会触发一个 lint,non_fmt_panics。自 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)

对于包含大括号但参数数量不正确的 panic 消息 (例如,panic!("Some curlies: {}")),您可以通过使用与 println! 相同的语法 (即 panic!("{}", "Some curlies: {}")) 或通过转义大括号 (即 panic!("Some curlies: {{}}")) 来使用字符串字面量 panic。