null は「値が存在しない」ことを表します。ただし実際のコードでは、まったく違う事情がすべて null に押し込まれています。
- まだ設定されていない
- 検索したが見つからなかった
- 意図的に空にした
- 取得に失敗した
- 参照する権限がない
これらを 1 つの値で表すと、受け取った側は「なぜ無いのか」を区別できません。現代の言語はこの曖昧さを削る方向に進んでいますが、その解き方は同じではありません。
4 つのモデル
値の欠如をどう表現するかで、言語は大きく 4 つに分かれます。
| モデル | 代表 | 書き方 | 欠如が検査される時点 |
|---|---|---|---|
| null を通常の値として許す | JavaScript | null / undefined |
実行時(規約頼み) |
| nullable を型で分離 | TypeScript, PHP, Python | User | null, ?User |
静的検査時 / 実行時 |
| 直和型で表す | Rust | Option<User> |
コンパイル時(分解が必須) |
| API 上の入れ物で包む | Java | Optional<User> |
実行時(null 自体は残る) |
重要なのは記号の違いではありません。値を使う前に、無い場合の処理を書いたかどうかを誰が確かめるか——そこが違います。
nullable 型は「分離」であって「強制」ではない
TypeScript と PHP は、値がある型と無い型を別の型として書き分けます。
function findUser(id: number): User | null {
return null;
}
function findUser(int $id): ?User
{
return null;
}
見た目は似ていますが、効き方が違います。TypeScript の strictNullChecks では、User | null のまま user.name と書くとコンパイルエラーになります。検査は開発時に済み、実行時に型は残りません。
PHP の ?User は逆に実行時に検査されます。宣言と違う値を返せば TypeError になりますが、それはコードが動いた瞬間の話です。
Python の User | None は、注釈を書いただけでは何も起きません。CPython は型注釈で自動的にエラーを出さないからです。mypy や Pyright を通して初めて検査になります。
def find_user(user_id: int) -> User | None:
return None
つまり同じ「nullable を型で分ける」でも、保証の強さは三段階あります。TypeScript は開発時に止まり、PHP は実行時に落ち、Python は型検査器を通したときだけ効きます。
Option 型は「分解しないと使えない」
Rust は値の有無を通常の型として表します。
enum Option<T> {
Some(T),
None,
}
fn find_user(id: u64) -> Option<User> {
None
}
決定的なのは、Option<User> から User を取り出すには必ず分解する必要がある点です。
match find_user(1) {
Some(user) => println!("{}", user.name),
None => println!("not found"),
}
nullable 型が「null かもしれないと表示する」のに対し、Option 型は「null の場合の処理を書かせる」。表示と強制の差だと考えると分かりやすいです。
null 安全構文は書き味の問題
一方で、?. や ?? といった構文はどの言語でも似ています。
$country = $user?->address?->country ?? 'unknown';
const country = user?.address?.country ?? "unknown";
let name = optional_name.unwrap_or("guest");
これらは記号が違うだけで、意味は共通しています。値があれば処理し、無ければ安全に別の値へ分岐する。言語間で本当に違うのは、この分岐を書き忘れたときに誰が気づくかであって、演算子の見た目ではありません。
まとめ
nullは「まだ無い」「見つからない」「失敗した」を区別せずに潰してしまう。曖昧さの原因はここにあります。- 「値がない」の表現は 4 つに分かれます。null をそのまま許す・型で分離する・直和型にする・入れ物で包む。
- nullable 型の保証の強さは言語ごとに違います。TypeScript は開発時、PHP は実行時、Python は型検査器を使ったときだけ効きます。
- Rust の
Option<T>は分解を義務づけるため、欠損の処理漏れがコンパイル時に残りません。 ?.や??は書き味の話です。言語選択で効いてくるのは、書き忘れを検出する時点のほうです。