簡體   English   中英

為什么Rust有“Never”原始類型?

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

Rust的std::process::exit具有類型

pub fn exit(code: i32) -> !

哪里! “從不” 原始類型

為什么Rust需要特殊類型呢?

哈斯克爾比較這其中的類型System.Exit.exitWith

exitWith :: forall a. Int -> a

相應的Rust簽名將是

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

沒有必要為不同的T單形化這個函數,因為T從未實現,因此編譯應該仍然有效。

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類型的方法與使用一個庫的庫一樣多種多樣。

生銹, ! 是一流的構造,統一所有圖書館,最重要的是......甚至沒有創建的圖書館! 記可以只是工作。

最好的例子是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類型使開發人員的意圖變得清晰,允許其他各方清楚地理解開發人員的意思,而不是必須從偶然的線索中重構意義。

我認為Rust需要特殊類型的原因! 包括:

  1. 表面語言沒有提供任何方式來寫type Never = for<T>(T)類似於type Never = forall a. a type Never = forall a. a在Haskell。

    更一般地說,在類型別名中,不能在RHS上使用類型變量(也就是通用參數)而不在LHS上引入它們,這正是我們想要在這里做的。 使用空結構/枚舉沒有意義,因為我們在這里需要一個類型別名,以便Never可以與任何類型統一,而不是新構造的數據類型。

    由於這種類型不能由用戶定義,因此它提供了將其作為基元添加可能有意義的一個原因。

  2. 如果在語法上允許為RHS分配一個非單變量類型(例如forall a. a a.a),編譯器將需要在調用約定時進行任意選擇(如注釋中的trentcl所指出的),即使選擇並不重要。 Haskell和OCaml可以回避這個問題,因為它們使用統一的內存表示。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM