为什么Rust具有“从不”原始类型?

防锈剂

锈的std::process::exit类型

pub fn exit(code: i32) -> !

其中!“从不”原语类型

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

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

exitWith :: forall a. Int -> a

相应的Rust签名为

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

无需针对不同T对该函数进行单态化,因为aT从未实现,因此编译仍然可以正常进行。

马修M.

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类型可以使开发人员的意图明确,让其他方可以清楚地了解开发人员的含义,而不必从偶然的线索中重新构造含义。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么 Rust 中的原始类型之间没有隐式类型转换(强制)

为什么原始类型不能具有带有通用返回类型的方法

为什么String没有原始类型?

为什么Kotlin使用===如果它们具有相同的值,则比较原始类型彼此相等

为什么原始数据类型具有固定大小?

为什么Rust的示例猜测游戏允许匹配语句具有不同的返回类型?

为什么在实现具有关联类型的关联const时,Rust为什么要对Self进行大小调整?

为什么类型别名不能使用Rust中原始类型的关联常量?

为什么`str`是原始类型?

为什么这会返回“从不”类型?

为什么Rust禁止现有类型的实现?

为什么Rust具有结构和枚举?

为什么eclipse说null是原始类型?

为什么DuplicateRecordFields不能具有类型推断?

为什么在此示例中参数b具有类型a?

为什么具有静态main()类型的程序显示错误?

为什么(。)地图具有此类型?

为什么LINQ方法具有可选类型

为什么最小的整数−2147483648具有类型'long'?

为什么文件夹具有“ b”类型变量?

为什么ParsecT类型具有'u'参数?

为什么std :: visit必须具有单个返回类型?

为什么参数“ props”隐式具有“ any”类型?

为什么函数迅速具有多种返回类型?

为什么python具有不同类型的字节

具有原始类型的反射映射

具有原始类型的JNI GetObjectClass

为什么打字稿会推断“从不”而不是交点类型?

TypeScript - 为什么数组 .includes() 期望 searchElement 是“从不”类型?