TL;DR:因为它支持局部推理和可组合性。
您将exit() -> ! 替换为exit<T>() -> T 的想法仅考虑了类型系统和类型推断。你是对的,从类型推断的角度来看,两者都是等价的。然而,语言不仅仅是类型系统。
无意义代码的局部推理
! 的存在允许本地推理来检测无意义的代码。例如,考虑:
use std::process::exit;
fn main() {
exit(3);
println!("Hello, World");
}
编译器立即标记println! 语句:
warning: unreachable statement
--> src/main.rs:5:5
|
5 | println!("Hello, World");
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(unreachable_code)] on by default
= note: this error originates in a macro outside of the current crate
(in Nightly builds, run with -Z external-macro-backtrace for more info)
怎么样?好吧,exit 的签名清楚地表明它永远不会返回,因为永远不会创建 ! 的实例,因此不可能执行它之后的任何内容。
优化的局部推理
同样,rustc 将有关 exit 签名的信息传递给 LLVM 优化器。
首先在exit的声明中:
; std::process::exit
; Function Attrs: noreturn
declare void @_ZN3std7process4exit17hcc1d690c14e39344E(i32) unnamed_addr #5
然后在使用现场,以防万一:
; playground::main
; Function Attrs: uwtable
define internal void @_ZN10playground4main17h9905b07d863859afE() unnamed_addr #0 !dbg !106 {
start:
; call std::process::exit
call void @_ZN3std7process4exit17hcc1d690c14e39344E(i32 3), !dbg !108
unreachable, !dbg !108
}
可组合性
在 C++ 中,[[noreturn]] 是一个属性。这真的很不幸,因为它没有与通用代码集成:对于有条件的noreturn 函数,您需要通过箍,并且选择noreturn 类型的方法与使用的库一样多。
在 Rust 中,! 是一流的构造,在所有库中都是统一的,最重要的是......即使在没有 ! 的情况下创建的库也可以正常工作。
最好的例子是Result 类型(Haskell 的Either)。它的完整签名是Result<T, E>,其中T 是预期类型,E 是错误类型。 ! 在Result 中没有什么特别之处,但可以用! 实例化:
#![feature(never_type)]
fn doit() -> Result<i32, !> { Ok(3) }
fn main() {
doit().err().unwrap();
println!("Hello, World");
}
编译器可以看穿它:
warning: unreachable statement
--> src/main.rs:7:5
|
7 | println!("Hello, World");
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(unreachable_code)] on by default
= note: this error originates in a macro outside of the current crate
(in Nightly builds, run with -Z external-macro-backtrace for more info)
可组合性(二)
推理无法实例化的类型的能力还扩展到推理无法实例化的枚举变体。
例如下面的程序编译:
#![feature(never_type, exhaustive_patterns)]
fn doit() -> Result<i32, !> {
Ok(3)
}
fn main() {
match doit() {
Ok(v) => println!("{}", v),
// No Err needed
}
// `Ok` is the only possible variant
let Ok(v) = doit();
println!("{}", v);
}
通常,Result<T, E> 有两个变体:Ok(T) 和 Err(E),因此匹配必须考虑到这两个变体。
然而,由于! 不能被实例化,Err(!) 不能被实例化,因此Result<T, !> 有一个变体:Ok(T)。因此编译器只允许考虑Ok 的情况。
结论
编程语言比它的类型系统更多。
编程语言是关于开发人员将其意图传达给其他开发人员和机器。 Never 类型让开发者的意图更加清晰,让其他方可以清楚地理解开发者的意思,而不必从偶然的线索中重构意思。