- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
同じ1行でも、何が起きるかはその言語が約束した分しか決まっていません。今回は「無い」という約束の違いを、3つの言語で並べて動かします。
まず結論
「値が無い」の表し方は言語ごとに違います。JavaScript は undefined と null という2つの値を持ち、Go は型ごとの nil を持ち、Rust は Option という型で「有るか無いか」を包みます。
型が「無い」を持つと、確かめ忘れを実行前に見つけられます。ただし失敗が消えるわけではなく、取り出す1か所へ移るだけです。Go の nil チェックも、何を nil と数えるかを知らないと素通りします。
Go で、中身が nil の `*MyErr` 型の変数 p を `error` 型の戻り値として返しました。呼び出し側で `find(true) == nil` を表示すると何が出るでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
「無い」を、値で持つか、型で持つか
関数が「見つからなかった」を返す、設定項目が「指定されていない」。どの言語にも「無い」を表す手段がありますが、その約束は大きく3つに分かれます。
1つ目は値として持つやり方です。JavaScript には「無い」を表す値が2つあり、仕様(ECMA-262、2026-09-20 のドラフト)は別々に定義しています。
4.4.13 undefined value — primitive value used when a variable has not been assigned a value
4.4.15 null value — primitive value that represents the intentional absence of any object value
「まだ代入されていない」と「意図して無い」。定義の段階で意味が違います。
2つ目は型ごとに nil を持つやり方で、Go がこれです。ポインタにも map にもインタフェースにも nil がありますが、同じ nil でも、何ができて何ができないかは型ごとに違います。
3つ目は型で包むやり方で、Rust の Option がこれです。Option<i32> は「i32 が有る(Some)か、無い(None)か」を表す別の型で、i32 そのものではありません。中身を使う前に有無を分ける書き方を、型が求めます。
- ・TypeScript は値の側(JavaScript)に、型の検査を設定で後から載せる。strictNullChecks が off なら null はどの型にも入る
JavaScript の2つの「無い」を並べる
Node v24.21.0(aarch64、Docker)で、次の6行を動かしました。
const o = { a: undefined };console.log("a" in o, "b" in o, o.a === o.b, null == undefined, null === undefined);console.log(JSON.stringify({ a: undefined, b: null }));console.log(typeof null, typeof undefined);function f(x = 5) { return x } console.log(f(undefined), f(null));console.log(0 ?? "d", 0 || "d", null ?? "d");
true false true true false{"b":null}object undefined5 null0 d d
1行目の出力が要点です。o.a と o.b は読むとどちらも undefined で === も true ですが、in で聞くと a は有り、b は無い。「undefined が入っている」と「キーが無い」は、読むだけでは区別できません。 null == undefined は true、=== では false です。
出力の4行目は、既定引数が undefined のときだけ効くことを示します。f(null) は null のままです。出力の5行目にある ?? も undefined と null だけを「無い」とみなし、0 はそのまま返します。|| は 0 も空とみなして "d" に置き換えます。「無い」の範囲が演算子ごとに違います。
出力2行目の、JSON にすると undefined のキーが消える件は、プロセスの外へ出るときの話なので第14期で扱います。
JavaScript で `function f(x = 5) { return x }` と定義したとき、`f(undefined)` と `f(null)` はそれぞれ何を返しましたか。
nil が型を持つ、Option が型である
Go に戻ります。Go FAQ は Why is my nil error value not equal to nil? という項を立てています。
Under the covers, interfaces are implemented as two elements, a type T and a value V.
An interface value is nil only if the V and T are both unset, (T=nil, V is not set), In particular, a nil interface will always hold a nil type.
error はインタフェースです。nil ポインタを入れると V は空でも T が埋まるので、nil と比べると等しくありません。「nil チェックを書けば安全」は、何が nil と数えられるかを知っていて初めて成り立ちます。
map の nil は別の約束です。仕様は A nil map is equivalent to an empty map except that no elements may be added. と書き、Assigning to an element of a nil map causes a run-time panic. と続けます。読めば空、書けば止まる。
Rust の Option は、この「確かめ忘れ」をコンパイル時に移します。ただし std の文書は unwrap について Panics if the self value equals None. と書きます。Option は失敗を消しません。確かめずに取り出す書き方を、目に見える1語(unwrap)にするだけです。 文書は同じ箇所で、パターンマッチで None を明示的に扱うか unwrap_or などを使うよう勧めています。
Go の型つき nil と、Rust の unwrap
Go 1.27.1 で、FAQ と同じ形を動かしました(抜粋)。
func find(ok bool) error {var p *MyErr = nilif ok { return p } // 型つきの nil を error として返すreturn nil}fmt.Println(find(false) == nil, find(true) == nil)var m map[string]intfmt.Println("nil map read:", m["x"], len(m))m["x"] = 1
true falsenil map read: 0 0true truerecovered: assignment to entry in nil map
find(true) == nil が false です。3行目は、この error から errors.As で *MyErr を取り出すと成功し、取り出した値は nil だったという表示です。「エラーがある」と判定されたのに、中身は nil のポインタです。 最終行は nil map への書き込みで、プログラムが止まります(実演では文言を表示するために受け止めています。止まる仕組みは第9回で扱います)。
rustc 1.98.1(既定の debug ビルド)では、空の Vec の先頭を取り出しました。
0thread 'main' (25) panicked at /w/main.rs:5:26:called `Option::unwrap()` on a `None` valuenote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
v.first() は Option<&i32> を返します。unwrap_or(0) は無いときの値を自分で決めたので 0 を表示し、unwrap() はそこで止まりました(この環境では exit 101)。止まった場所は main.rs:5:26、つまり unwrap を書いた場所そのものです。失敗は起きていますが、どこで確かめ忘れたかは1か所に特定されています。
「Rust で Option を使えば、値が無いせいでプログラムが止まることは無くなる」という主張の扱いとして適切なのはどれですか。
Go:型つき nil を error で返す
find(true) == nil は false。error インタフェースに *MyErr の型が入ったため。呼び出し側の nil チェックは書かれているのに、成功を失敗と判定する。コンパイラは何も言わない。
true false
Rust:None に unwrap を呼ぶ
unwrap_or(0) は 0 を返し、unwrap() は panicked at main.rs:5:26 で止まる。止まる場所は unwrap を書いた行。書かなければコンパイラが有無の分岐を求める。
0thread 'main' (25) panicked at /w/main.rs:5:26:
どちらもプログラマは「無い場合」を意識して書いています。それでも結果が食い違う理由は、どこにあるでしょうか。
「確かめずに取り出す」を、それぞれのコードのどの文字列から読み取れるかを探してください。
この章のまとめ
- JavaScript の undefined と null は仕様で定義の違う2つの値。既定引数と
??は undefined/null だけを、||は 0 も「無い」とみなす。 { a: undefined }の a と、存在しない b は、読むと同じ、inで聞くと違う。- Go のインタフェースは型と値の2要素で、両方が空のときだけ nil。nil ポインタを error で返すと
!= nilになる。 - Go の nil map は読めば空、書けば止まる。
- Rust の Option は確かめ忘れをコンパイル時に見つけるが、unwrap は None で止まる。失敗は消えず、書いた1か所に移る。
- TypeScript の null の検査は strictNullChecks しだい(
--strictに含まれる。既定は 5.9.3 で false、6.0 以降で true)。off なら null はどの型にも入る。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
Go で `var m map[string]int` と宣言しただけの m について、仕様と実演に合うものはどれですか。
用語集(4語)
- undefined と null(JavaScript)
- 別々のプリミティブ値。ECMA-262 は undefined を「変数に値が代入されていないときに使われる値」、null を「オブジェクト値が意図して無いことを表す値」と定義する。
- nil インタフェース(Go)
- インタフェースの値は型 T と値 V の2要素で、両方が空のときだけ nil。nil ポインタを入れると T が埋まり、nil と等しくなくなる。
- Option(Rust)
- Some(値) か None のどちらかを持つ型。中身を使うには有無の分岐を書くか、unwrap_or などで無いときの値を決める。unwrap は None で止まる(仕組みは第9回で扱う)。
- strictNullChecks(TypeScript)
- null と undefined の扱いを切り替える設定。off では両者を任意の型に代入でき、on では値を使う前に null・undefined かを確かめる必要がある。
出典(一次ソース)
- Go FAQ — Why is my nil error value not equal to nil? ↗
- The Go Programming Language Specification (Language version go1.27 (May 26, 2026)) — Map types / Index expressions ↗
- ECMAScript 2027 Language Specification, Draft September 20, 2026 — 4.4.13 undefined value / 4.4.15 null value ↗
- Rust std 1.98.1 — enum Option(unwrap の Panics 節) ↗
- TSConfig Reference — strict / strictNullChecks ↗
- Announcing TypeScript 6.0(Simple Default Changes: strict is now true by default) ↗
- TypeScript Handbook — Everyday Types(null and undefined) ↗