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
进行单一化处理,因为T
从未实现,因此编译仍然可以进行。
答案 0 :(得分:15)
您将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类型可以使开发人员的意图明确,使其他方可以清楚地了解开发人员的含义,而不必从偶然的线索中重新构造含义。
答案 1 :(得分:3)
我认为Rust需要特殊类型!
的原因包括:
表面语言没有提供类似于type Never = for<T>(T)
的{{1}}编写方式。
更一般地说,在类型别名中,如果不在 LHS 上引入类型变量(即通用参数),就不能在RHS上使用它们,这正是我们在此要做的。使用空的struct / enum没有意义,因为我们在这里需要类型别名,以便type Never = forall a. a
可以与任何类型统一,而不是与新构造的数据类型统一。
由于用户无法定义此类型,因此提出了将其添加为原语可能有意义的一个原因。
如果在语法上允许一个人为RHS分配一个非单态类型(例如Never
),则编译器将需要做出一个任意选择w.r.t。调用约定(如trentcl在评论中指出的那样),即使选择并不重要。 Haskell和OCaml可以回避此问题,因为它们使用统一的内存表示形式。
答案 2 :(得分:0)
做些补充:
Never
在 Rust 中是底部类型,Haskell 也有底部类型,只是换了个名字。
在类型理论中,数理逻辑中的一种理论,最底层的类型是没有值的类型。也称为零型或空型,有时用上标(⊥)符号表示。
https://en.wikipedia.org/wiki/Bottom_type
大多数常用语言都没有明确表示 empty type
的方法,但它们隐含地拥有它,因为代码可能会出现恐慌或无限循环。
和网站更多内容:
<块引用>最常用的语言没有办法明确表示 空型。有几个值得注意的例外。
从 Haskell2010 开始,Haskell 支持空数据类型。因此,它允许 定义数据为空(没有构造函数)。空类型是 不是很空,因为它包含非终止程序和 未定义的常量。 undefined 常量通常在需要时使用 有空类型的东西,因为 undefined 匹配任何类型 (所以是所有类型的“子类型”),并试图评估 undefined 将导致程序中止,因此它永远不会返回 一个答案。
在 Scala 中,底部类型表示为 Nothing。除了用于 只抛出异常或不返回的函数 通常,它也用于协变参数化类型。为了 例如,Scala 的 List 是一个协变类型构造函数,所以 List[Nothing] 是所有类型 A 的 List[A] 的子类型。所以 Scala 的 Nil, 用于标记任何类型列表结尾的对象,属于 输入 List[Nothing]。
在 Rust 中,底部类型称为 never 类型,用 ! 表示。 它存在于保证永远不会的函数的类型签名中 返回,例如通过调用 panic!() 或永远循环。也是 某些控制流关键字的类型,例如 break 和 return, 不产生价值但仍可用作 表达式。[4]
在 TypeScript 中,底部类型从不。[7][8]
在带有闭包编译器注解的 JavaScript 中,底部类型是 !Null(字面意思是 Null 单元类型的非空成员)。
在 PHP 中,底部类型是 never。
在 Python 中,最底层的类型是 Typing.NoReturn.[9]