为什么Rust有"Never"原始类型?

cut*_*lus 8 types rust

Rust的std::process::exit类型

pub fn exit(code: i32) -> !
Run Code Online (Sandbox Code Playgroud)

哪里!"从不" 原始类型.

为什么Rust需要特殊类型呢?

将其与Haskell进行比较,其类型System.Exit.exitWith

exitWith :: forall a. Int -> a
Run Code Online (Sandbox Code Playgroud)

相应的Rust签名将是

pub fn exit<T>(code: i32) -> T
Run Code Online (Sandbox Code Playgroud)

没有必要将此函数单独化为不同T的函数,因为a T从未实现,因此编译仍然可以工作.

Mat*_* M. 15

TL; DR:因为它支持本地推理和可组合性.

您更换的想法exit() -> !通过exit<T>() -> T只考虑类型系统和类型推断.你是对的,从类型推断的角度来看,两者都是等价的.然而,语言比类型系统更多.

无意义代码的本地推理

的存在!允许本地推理检测荒谬的代码.例如,考虑:

use std::process::exit;

fn main() {
    exit(3);
    println!("Hello, World");
}
Run Code Online (Sandbox Code Playgroud)

编译器立即标记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)
Run Code Online (Sandbox Code Playgroud)

怎么样?好吧,exit它的签名清楚地表明它永远不会返回,因为!永远不会创建任何实例,因此在它之后的任何事情都不可能被执行.

优化的本地推理

类似地,rustc将有关exitLLVM优化器签名的信息传递给它.

首先在宣言中exit:

; std::process::exit
; Function Attrs: noreturn
declare void @_ZN3std7process4exit17hcc1d690c14e39344E(i32) unnamed_addr #5
Run Code Online (Sandbox Code Playgroud)

然后在使用现场,以防万一:

; 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
}
Run Code Online (Sandbox Code Playgroud)

组合性

在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");
}
Run Code Online (Sandbox Code Playgroud)

编译器通过它看到了:

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)
Run Code Online (Sandbox Code Playgroud)

可组合性(之二)

推理无法实例化的类型的能力也扩展到无法实例化的枚举变体的推理.

例如,以下程序编译:

#![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);
}
Run Code Online (Sandbox Code Playgroud)

通常,Result<T, E>有两种变体:Ok(T)Err(E),因此匹配必须考虑到两种变体.

然而,在这里,由于!无法实例化,Err(!)因此无法进行,因此Result<T, !>具有单一变体:Ok(T).因此编译器只允许考虑这种Ok情况.

结论

编程语言比其类型系统更多.

编程语言是关于开发人员将其意图传达给其他开发人员和机器的.Never类型使开发人员的意图变得清晰,允许其他各方清楚地理解开发人员的意思,而不是必须从偶然的线索中重构意义.