永不类型回退变更
概述
- 永不类型 (
!
) 到任何类型(“永不转任何”)的强制转换会回退到永不类型 (!
),而不是单元类型 (()
)。 never_type_fallback_flowing_into_unsafe
lint 现在默认是deny
。
详情
当编译器在 强制转换点 中看到类型为 !
(never) 的值时,它会隐式插入一个强制转换,以允许类型检查器推断任何类型
#![allow(unused)] fn main() { #![feature(never_type)] // This: let x: u8 = panic!(); // ...is (essentially) turned by the compiler into: let x: u8 = absurd(panic!()); // ...where `absurd` is the following function // (it's sound because `!` always marks unreachable code): fn absurd<T>(x: !) -> T { x } }
如果类型无法推断,这可能会导致编译错误
#![allow(unused)] fn main() { #![feature(never_type)] fn absurd<T>(x: !) -> T { x } // This: { panic!() }; // ...gets turned into this: { absurd(panic!()) }; //~ ERROR can't infer the type of `absurd` }
为了防止此类错误,编译器会记住它在何处插入了 absurd
调用,如果它无法推断类型,则会使用回退类型代替
#![allow(unused)] fn main() { #![feature(never_type)] fn absurd<T>(x: !) -> T { x } type Fallback = /* An arbitrarily selected type! */ !; { absurd::<Fallback>(panic!()) } }
这就是所谓的“永不类型回退”。
从历史上看,回退类型一直是 ()
(单元类型)。这导致 !
自发地强制转换为 ()
,即使编译器在没有回退的情况下也不会推断出 ()
。这令人困惑,并阻碍了 !
类型的稳定。
在 2024 版本中,回退类型现在是 !
。(我们计划在稍后日期在所有版本中进行此更改。)这使得事情更直观地工作。现在,当您传递 !
并且没有理由将其强制转换为其他类型时,它将保持为 !
。
在某些情况下,您的代码可能依赖于回退类型为 ()
,因此这可能会导致编译错误或行为更改。
never_type_fallback_flowing_into_unsafe
never_type_fallback_flowing_into_unsafe
lint 的默认级别已在 2024 版本中从 warn
提升到 deny
。此 lint 有助于检测与回退到 !
和 unsafe
代码的特定交互,这可能会导致未定义的行为。有关完整说明,请参见链接。
迁移
没有自动修复,但是可以自动检测到会被版本更改破坏的代码。在旧版本上,您会看到警告,提示您的代码将被破坏。
修复方法是显式指定类型,以便不使用回退类型。不幸的是,可能不容易看出需要指定哪种类型。
此更改破坏的最常见模式之一是使用 f()?;
,其中 f
在返回类型的 Ok
部分上是泛型的
#![allow(unused)] fn main() { #![allow(dependency_on_unit_never_type_fallback)] fn outer<T>(x: T) -> Result<T, ()> { fn f<T: Default>() -> Result<T, ()> { Ok(T::default()) } f()?; Ok(x) } }
您可能会认为,在此示例中,无法推断类型 T
。但是,由于当前 ?
运算符的脱糖,它被推断为 ()
,现在将被推断为 !
。
要解决此问题,您需要显式指定 T
类型
#![allow(unused)] fn main() { #![deny(dependency_on_unit_never_type_fallback)] fn outer<T>(x: T) -> Result<T, ()> { fn f<T: Default>() -> Result<T, ()> { Ok(T::default()) } f::<()>()?; // ...or: () = f()?; Ok(x) } }
另一个相对常见的案例是在闭包中 panic
#![allow(unused)] fn main() { #![allow(dependency_on_unit_never_type_fallback)] trait Unit {} impl Unit for () {} fn run<R: Unit>(f: impl FnOnce() -> R) { f(); } run(|| panic!()); }
以前,来自 panic!
的 !
强制转换为 ()
,后者实现了 Unit
。但是现在 !
保持为 !
,因此此代码失败,因为 !
未实现 Unit
。要解决此问题,您可以指定闭包的返回类型
#![allow(unused)] fn main() { #![deny(dependency_on_unit_never_type_fallback)] trait Unit {} impl Unit for () {} fn run<R: Unit>(f: impl FnOnce() -> R) { f(); } run(|| -> () { panic!() }); }
当在一个分支中使用 !
类型的表达式,而在另一个分支中使用具有无约束返回类型的函数时,可以看到与 f()?
类似的情况
#![allow(unused)] fn main() { #![allow(dependency_on_unit_never_type_fallback)] if true { Default::default() } else { return }; }
以前,()
被推断为 Default::default()
的返回类型,因为来自 return
的 !
被错误地强制转换为 ()
。现在,将推断 !
,导致此代码无法编译,因为 !
未实现 Default
。
同样,可以通过显式指定类型来解决此问题
#![allow(unused)] fn main() { #![deny(dependency_on_unit_never_type_fallback)] () = if true { Default::default() } else { return }; // ...or: if true { <() as Default>::default() } else { return }; }