TL;DR:因为它支持局部推理和可组合性.
用exit<T>() -> T
替换exit() -> !
的 idea 只考虑类型系统和类型推断.你是对的,从类型推断的Angular 来看,两者是等价的.然而,语言不仅仅是类型系统.
无意义代码的局部推理
!
的存在允许局部推理检测无意义的代码.例如,考虑:
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
的签名表明它永远不会返回,因为!
的任何实例都不能被创建,所以它之后的任何东西都不可能被执行.
优化的局部推理
类似地,LLC的签名会将关于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
函数,你需要经历很多困难, Select noreturn
类型的方法和使用noreturn
类型的库一样多种多样.
在Rust中,!
是一个一流的构造,在所有库中都是统一的,最棒的是...即使创建了没有!
个的库,也可以正常工作.
最好的例子是Result
型(Haskell's 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)
可组合性 (bis)
推理无法实例化的类型的能力也扩展到推理无法实例化的枚举变量.
例如,以下程序编译:
#![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
的情况.
结论
编程语言不仅仅是它的类型系统.
编程语言是关于一个开发人员与其他开发人员和机器之间的通信."从不"类型使开发人员的意图清晰明了,让其他各方能够清楚地理解开发人员的意思,而不必根据偶然的线索重建其含义.