永不类型回退变更

概述

  • 永不类型 (!) 到任何类型(“永不转任何”)的强制转换会回退到永不类型 (!),而不是单元类型 (())。
  • 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
};
}