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 中会出错的已弃用行为时,都会触发 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)
。