🎄

型における空集合、never型とは

2023/12/10に公開

never型とは

never型は、「決して観測されない値」、「該当する値が存在しない」ことを表す型です。
never型の変数にはany型の値であっても代入することはできません。

const anyValue: any = 271
const neverValue: never = anyValue //Type 'any' is not assignable to type 'never'.(2322)

never型の変数を引数に設定した場合には渡せる引数が存在しないため[1]、関数を呼び出すことができなくなります。

function neverArgFunction(arg: never){
    return 314
}

const num = neverArgFunction(1) //Argument of type 'number' is not assignable to parameter of type 'never'.(2345)
const numAsNever = neverArgFunction(1 as never) //型アサーションによってコンパイルエラーを握りつぶすことはできます。
console.log(numAsNever) //314 

never型はあらゆる型の部分型

型注釈や型アサーションによってnever型の変数を用意した場合に、このnever型の変数は、あらゆる型の変数に代入することが可能です。
このことから、never型があらゆる型の部分型として扱われていることがわかります。集合論において、空集合はあらゆる集合の部分集合ですが、never型も同様の立ち位置にいるわけですね。

const neverValue: never = 1 as never
const str: string =  neverValue
const obj:{str: string, num: number} = neverValue

function neverArgFunction(arg: never){
    const num: number = arg
    const obj: {str: string, num: number} = arg
    return 314
}
// コンパイルエラーが発生しない

never型とユニオン型

number | neverのようなneverとのユニオン型については、neverの部分が無視された形の型となります。

type UnionNever = string | number | never

const num: UnionNever = 1

型注釈:UnionNeverをつけてVS Codeなどでカーソルを合わせてみると、type UnionNever = string | numberのようにnever部分が無視されて表示されます。
never型を空集合と捉えると、他の集合Aとの和集合|をとったときにその結果が集合A自身となることに対応していると捉えられると思います。

また、never型は、ユニオン型の変数について型情報で条件分岐させていったときに、ユニオンのすべての型の分岐が出尽くした後の変数の型として出現します。
以下のコードではtypeofによる条件分岐によって、最後のブロックのargにはstring型の値もnumber型の値もあてはまらないこととなります。
引数はstring型かnumber型であると型注釈が付けられていますので、最後のブロックのargは存在し得ない値、つまりnever型が割り当てられることになります。

function fn(arg: string | number) {
  if (typeof arg === "string") {
    console.log(arg) //(parameter) arg: string
  } else if (typeof arg === "number") {
    console.log(arg) //(parameter) arg: number
  } else {
    console.log(arg) //(parameter) arg: never
  }
}

never型とインターセクション型

インターセクション型とは、型&型のように表される型で、&で結ばれたすべての型の条件をみたすような型を表します。集合論でいえば、積集合(「AかつB」)に相当するものですね。
以下のコードでは「squidプロパティが足りない」とコンパイルエラーが出ています🦑

type Octopus = {octopus: string}
type Squid = {squid: string}

const obj: Octopus & Squid = {octopus:'blue-ringed'}
//Type '{ octopus: string; }' is not assignable to type 'Octopus & Squid'.
//Property 'squid' is missing in type '{ octopus: string; }' but required in type 'Squid'.(2322)

never型とのインターセクションはすべてnever型となります。
これは、空集合との積集合(共通部分)が常に空集合となることに対応しています。

type T = number & never //type T = never

また、string型とnumber型のように両方の型に合致する値が存在しないような型の組み合わせでインターセクションをとると、こちらも常にnever型となります。
こちらも共通の要素を持たない集合同士の積集合をとると空集合となることに対応していますね。

type T = number & string //type T = never

never型を返す関数

常に例外を投げるなど、値を返さない関数の返り値の型としてneverを使用できます。
例外を投げる関数の返り値を取得することは不可能なため、存在しない値が返ってくるとみなせて、never型を設定してもコンパイルエラーが起きないわけですね。

function throwError(): never {
  throw new Error();
}
const neverValue:never = throwError() //コンパイルエラーは発生しない。

上のコードではthrowErrorの返り値にnever型の型注釈をつけなかった場合にはvoid型が返ってくると型推論されてしまい、代入部分でコンパイルエラーが発生します。
Type 'void' is not assignable to type 'never'.(2322)

また、never型を返り値とする関数は、ブロックの最後まで到達できないように記述されている必要があります。ブロックの最後まで到達する可能性がある場合にはコンパイルエラーが出るようになっています。

function throwError(str: string):never {
    if(str.length < 20){
        throw new Error(str)
    }
}
//A function returning 'never' cannot have a reachable end point.(2534)

neverについての公式Docsはこちら↓
https://www.typescriptlang.org/docs/handbook/2/functions.html#never

脚注
  1. 型アサーションas neverなどで型安全性を破壊すればその限りではないですが。 ↩︎

GitHubで編集を提案

Discussion