Rust's std::process::exit有这种类型

pub fn exit(code: i32) -> !

其中!"Never" primitive type.

为什么Rust 需要一种特殊的类型?

将此与Haskell进行比较,后者的类型为System.Exit.exitWith

exitWith :: forall a. Int -> a

相应的 rust 迹是

pub fn exit<T>(code: i32) -> T

没有必要将这个函数单态化为不同的T,因为T永远不会具体化,所以编译应该仍然有效.

推荐答案

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的情况.

结论

编程语言不仅仅是它的类型系统.

编程语言是关于一个开发人员与其他开发人员和机器之间的通信."从不"类型使开发人员的意图清晰明了,让其他各方能够清楚地理解开发人员的意思,而不必根据偶然的线索重建其含义.

Rust相关问答推荐

把Vector3变成Vector4的绝妙方法

在函数内定义impl和在函数外定义impl的区别

带扫描的铁 rust 使用滤镜

有没有办法避免在While循环中多次borrow `*分支`

如何正确地将App handler传递给Tauri中的其他模块?

如何编写一个以一个闭包为参数的函数,该函数以另一个闭包为参数?

在铁 rust 中传递所有权

为昂贵的for循环制作筛子

`RwLockWriteGuard_,T`不实现T实现的特征

如何实现Deref;多次;?

为什么rustc会自动降级其版本?

为什么我必须使用 PhantomData?在这种情况下它在做什么?

带引脚和不带引脚的比较功能

需要哪些编译器优化来优化此递归调用?

我如何取消转义,在 Rust 中多次转义的字符串?

如何在 Rust 中将枚举变体转换为 u8?

了解 Rust 闭包:为什么它们持续持有可变引用?

返回引用字符串的future

如何在 Rust 中创建最后一个元素是可变长度数组的 struct ?

为什么 u64::trailing_zeros() 在无分支工作时生成分支程序集?