简体   繁体   English

为什么Rust有“Never”原始类型?

[英]Why does Rust have a “Never” primitive type?

Rust's std::process::exit has the type Rust的std::process::exit具有类型

pub fn exit(code: i32) -> !

where ! 哪里! is the "Never" primitive type . “从不” 原始类型

Why does Rust need a special type for this? 为什么Rust需要特殊类型呢?

Compare this with Haskell where the type of System.Exit.exitWith is 哈斯克尔比较这其中的类型System.Exit.exitWith

exitWith :: forall a. Int -> a

The corresponding Rust signature would be 相应的Rust签名将是

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

There is no need to monomorphize this function for different T 's because a T is never materialized so compilation should still work. 没有必要为不同的T单形化这个函数,因为T从未实现,因此编译应该仍然有效。

TL;DR: Because it enables local reasoning, and composability. TL; DR:因为它支持本地推理和可组合性。

Your idea of replacing exit() -> ! 你想更换exit() -> ! by exit<T>() -> T only considers the type system and type inference. 通过exit<T>() -> T只考虑类型系统和类型推断。 You are right that from a type inference point of view, both are equivalent. 你是对的,从类型推断的角度来看,两者都是等价的。 Yet, there is more to a language than the type system. 然而,语言比类型系统更多。

Local reasoning for nonsensical code 无意义代码的本地推理

The presence of ! 在场! allows local reasoning to detect nonsensical code. 允许本地推理检测无意义的代码。 For example, consider: 例如,考虑:

use std::process::exit;

fn main() {
    exit(3);
    println!("Hello, World");
}

The compiler immediately flags the println! 编译器立即标记println! statement: 声明:

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)

How? 怎么样? Well, exit 's signature makes it clear it will never return, since no instance of ! 好吧, exit的签名表明它永远不会返回,因为没有实例! can ever be created, therefore anything after it cannot possibly be executed. 可以创建,因此无法执行之后的任何事情。

Local reasoning for optimizations 优化的本地推理

Similarly, rustc passes on this information about the signature of exit to the LLVM optimizer. 类似地,rustc传递有关exit到LLVM优化器的签名的信息。

First in the declaration of exit : 首先在exit声明中:

; std::process::exit
; Function Attrs: noreturn
declare void @_ZN3std7process4exit17hcc1d690c14e39344E(i32) unnamed_addr #5

And then at the use site, just in case: 然后在使用现场,以防万一:

; 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
}

Composability 组合性

In C++, [[noreturn]] is an attribute. 在C ++中, [[noreturn]]是一个属性。 This is unfortunate, really, because it does not integrate with generic code: for a conditionally noreturn function you need to go through hoops, and the ways to pick a noreturn type are as varied as there are libraries using one. 实际上,这是不幸的,因为它没有与通用代码集成:对于有条件的noreturn函数,你需要经历箍,并且选择noreturn类型的方法与使用一个库的库一样多种多样。

In Rust, ! 生锈, ! is a first-class construct, uniform across all libraries, and best of all... even libraries created without ! 是一流的构造,统一所有图书馆,最重要的是......甚至没有创建的图书馆! in mind can just work. 记可以只是工作。

The best example is the Result type (Haskell's Either ). 最好的例子是Result类型(Haskell的Either )。 Its full signature is Result<T, E> where T is the expected type and E the error type. 它的完整签名是Result<T, E> ,其中T是预期类型, E是错误类型。 There is nothing special about ! 没什么特别的! in Result , yet it can be instantiated with ! Result ,它可以实例化! :

#![feature(never_type)]

fn doit() -> Result<i32, !> { Ok(3) }

fn main() {
    doit().err().unwrap();
    println!("Hello, World");
}

And the compiler sees right through it: 编译器通过它看到了:

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)

Composability (bis) 可组合性(之二)

The ability to reason about types that cannot be instantiated also extends to reasoning about enum variants that cannot be instantiated. 推理无法实例化的类型的能力也扩展到无法实例化的枚举变体的推理。

For example, the following program compiles: 例如,以下程序编译:

#![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);
}

Normally, Result<T, E> has two variants: Ok(T) and Err(E) , and therefore matching must account for both variants. 通常, Result<T, E>有两个变体: Ok(T)Err(E) ,因此匹配必须考虑两种变体。

Here, however, since ! 但是,从那以后! cannot be instantiated, Err(!) cannot be, and therefore Result<T, !> has a single variant: Ok(T) . 无法实例化, Err(!)不能,因此Result<T, !>有一个变体: Ok(T) The compiler therefore allows only considering the Ok case. 因此编译器只允许考虑Ok情况。

Conclusion 结论

There is more to a programming language than its type system. 编程语言比其类型系统更多。

A programming language is about a developer communicating its intent to other developers and the machine. 编程语言是关于开发人员将其意图传达给其他开发人员和机器的。 The Never type makes the intent of the developer clear, allowing other parties to clearly understand what the developer meant, rather than having to reconstruct the meaning from incidental clues. Never类型使开发人员的意图变得清晰,允许其他各方清楚地理解开发人员的意思,而不是必须从偶然的线索中重构意义。

I think the reasons why Rust needs a special type ! 我认为Rust需要特殊类型的原因! include: 包括:

  1. The surface language doesn't offer any way to write type Never = for<T>(T) analogous to type Never = forall a. a 表面语言没有提供任何方式来写type Never = for<T>(T)类似于type Never = forall a. a type Never = forall a. a in Haskell. type Never = forall a. a在Haskell。

    More generally, in type aliases, one cannot use type variables (aka generic parameters) on the RHS without introducing them on the LHS , which is precisely what we want to do here. 更一般地说,在类型别名中,不能在RHS上使用类型变量(也就是通用参数)而不在LHS上引入它们,这正是我们想要在这里做的。 Using an empty struct/enum doesn't make sense because we want a type alias here so that Never can unify with any type, not a freshly constructed data type. 使用空结构/枚举没有意义,因为我们在这里需要一个类型别名,以便Never可以与任何类型统一,而不是新构造的数据类型。

    Since this type cannot be defined by the user, it presents one reason why adding it as a primitive may make sense. 由于这种类型不能由用户定义,因此它提供了将其作为基元添加可能有意义的一个原因。

  2. If one is syntactically allowed to assign a non-monomorphizable type to the RHS (such as forall a. a ), the compiler will need to make an arbitrary choice wrt calling conventions (as pointed out by trentcl in the comments), even though the choice doesn't really matter. 如果在语法上允许为RHS分配一个非单变量类型(例如forall a. a a.a),编译器将需要在调用约定时进行任意选择(如注释中的trentcl所指出的),即使选择并不重要。 Haskell and OCaml can sidestep this issue because they use a uniform memory representation. Haskell和OCaml可以回避这个问题,因为它们使用统一的内存表示。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM