【入門編】Dartの定数(const)で定義できるものとできないものの境界線 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは!Dartの深淵へようこそ。

今日は、Dartを学び始めたばかりの方が必ずと言っていいほど直面する、そして中級者になっても時折「あれ?」と手が止まってしまう『const』の境界線についてお話しします。

「`final`と`const`って何が違うの?」「なぜこのコードはエラーになるの?」
そんな疑問を抱えているなら、この記事はまさにあなたのためのものです。Dart VMの内部で何が起きているのかを少しだけ覗き見しながら、本質的な違いをマスターしていきましょう。

ここをクリアすれば、Dartの基本はバッチリマスターできますよ。

—

1. 似ているようで全く違う「3つの宣言」

まずは基本の整理からです。Dartには変数を宣言する方法が主に3つありますが、それぞれ「いつ値が決まるか」という時間軸が異なります。

| キーワード | 再代入 | 値が確定するタイミング | 特徴 |
| :— | :— | :— | :— |
| var | 可能 | 実行時(Runtime) | 一般的な変数。型推論が効く。 |
| final | 不可 | 実行時(Runtime) | 一度決めたら変えられない「読み取り専用」。 |
| const | 不可 | コンパイル時 | 実行前から値が決まっている「不変の定数」。 |

ここで最も重要なのは、`const`は「プログラムが動き出す前(コンパイル時)」に、その値が完全に確定していなければならないという点です。

—

2. 「const」になれるもの、なれないものの境界線

「コンパイル時に確定する」とはどういうことでしょうか? 具体的なコードでその境界線を見てみましょう。

〇 constになれる(コンパイル時定数)

リテラル(数値、文字列、真偽値)や、それらを組み合わせた計算式はOKです。

// 数値や文字列の計算(コンパイラが計算できる)
const speedOfLight = 299792458;
const greeting = ‘Hello, ‘ + ‘Dart!’;

// 他のconstを使った計算
const double pi = 3.14159;
const double radius = 10.0;
const double area = pi radius radius; // 全てconstなのでOK

× constになれない(実行時まで不明なもの)

「実行してみないとわからない値」は、絶対に`const`にはできません。

// 現在時刻は実行するたびに変わるため、コンパイル時には決められません
// const now = DateTime.now(); // ❌ コンパイルエラー!
final now = DateTime.now(); // ✅ finalならOK

// 関数の戻り値も、基本的には実行時に決まるのでNG
int getNumber() => 42;
// const val = getNumber(); // ❌ コンパイルエラー!

—

3. 複雑なオブジェクトを定数化する「工夫」

Dartの面白いところは、リスト(List)や自作のクラス(Class)さえも`const`にできる点です。ただし、そこには厳格なルールがあります。

constコンストラクタの魔法

自作のクラスを`const`として扱うには、「constコンストラクタ」を定義する必要があります。

class User {
final String name;
final int age;

// 1. 全てのフィールドが「final」であること
// 2. コンストラクタに「const」がついていること
const User(this.name, this.age);
}

void main() {
// これで、このオブジェクトは「不変の定数」としてメモリに配置されます
const admin = User(‘Alice’, 30);
}

コレクションのconst

リストやマップも`const`にできますが、中身もすべて`const`でなければなりません。

// ✅ リスト全体を定数にする
const list = [1, 2, 3];

// ❌ 中身に変数やfinalが混じっているとダメ
final x = 10;
// const badList = [x, 20]; // ❌ xはconstではないのでエラー

—

4. なぜ「const」をこれほどまでに強調するのか?(プロの視点)

ここからは、Dart VMを知り尽くしたアーキテクトとしての視点をお伝えします。なぜ単なる「書き換え禁止」以上の意味があるのでしょうか。

それは、「カノニカル化(Canonicalization)」という仕組みがあるからです。

Dartコンパイラは、プログラムの中に全く同じ内容の`const`オブジェクトを見つけると、それらをメモリ上の全く同じ場所(アドレス)に集約します。

void main() {
const a = [1, 2];
const b = [1, 2];

// aとbは、メモリ上の全く同じ実体を参照しています!
print(identical(a, b)); // 結果: true
}

これがFlutterで非常に重要な役割を果たします。
FlutterのWidgetに`const`をつけると、画面が更新(リビルド)される際、フレームワークは「あ、これは前と同じ定数だね。作り直す必要はないな」と判断し、圧倒的なパフォーマンス向上をもたらします。

「constはメモリを節約し、アプリを高速化する魔法のキーワード」だと覚えておいてください。

—

5. よくある「落とし穴」と解決策

初心者が最もハマりやすいのが、「constの伝播」です。

class Point {
final int x;
const Point(this.x);
}

void main() {
// エラー例
// const point = Point(DateTime.now().millisecond);
// ↑ Point自体はconst対応でも、引数が「実行時」の値なのでアウトです。
}

解決のヒント:

  • 「この値はプログラムを書いている今、決まっているか?」を自問自答してください。
  • 決まっていないなら、迷わず `final` を使いましょう。
  • `final` は「一度だけ代入できる変数」、`const` は「永遠に変わらない値」です。

—

まとめ:Dartを掌握するための第一歩

1. var は自由、final は一度きりの約束、const は不変の真理。
2. const にできるのは、コンパイル時に値が確定するものだけ。
3. 自作クラスを const にするには、全てのフィールドを `final` にして `const` コンストラクタを作る。
4. const を使うと、Dart VMがメモリを最適化し、アプリがサクサク動くようになる。

最初はエラーに戸惑うこともあるでしょう。でも、それはコンパイラが「もっと効率的なコードが書けるよ!」と教えてくれている合図です。

この境界線を理解すれば、あなたの書くDartコードはより堅牢で、プロフェッショナルなものへと進化します。一歩ずつ、楽しみながらマスターしていきましょうね。応援しています!

タイトルとURLをコピーしました