Panic 宏一致性
概要
panic!(..)
现在始终使用format_args!(..)
,就像println!()
一样。panic!("{")
不再被接受,除非将{
转义为{{
。- 如果
x
不是字符串字面量,则panic!(x)
不再被接受。- 使用
std::panic::panic_any(x)
以使用非字符串有效负载 panic。 - 或者使用
panic!("{}", x)
来使用x
的Display
实现。
- 使用
- 这同样适用于
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。