Dartのメモリを掌握せよ:var, final, constが生む「静的」と「動的」の境界線
Dartを書く際、`var`、`final`、`const`を何となく使い分けていないだろうか。
「再代入しないなら`final`」「リテラルなら`const`」。教科書通りの回答だが、それだけでは不十分だ。Dart VMの挙動、そしてAOT(Ahead-of-Time)コンパイルの最適化機構を理解すれば、これらは単なる「キーワード」ではなく、メモリ管理と実行時コストを制御する「設計の武器」に変わる。
今日は、Dartの深淵、コンパイルの裏側から、現場で突き抜けたコードを書くための指針を授ける。
—
1. `const`:コンパイル時定数の真実
`const`の本質は「コンパイル時に値が確定し、メモリ上に唯一のインスタンスとして鎮座する」ことにある。
Dartのコンパイラは、`const`で宣言された値(数値、文字列、あるいは`const`コンストラクタを持つクラス)を、プログラムの実行バイナリのデータセグメントに埋め込む。
なぜ`const`を多用すべきなのか
例えば、ウィジェットツリーの中で頻繁に再生成されるUIパーツがあるとする。
// 悪い例:再評価のたびにメモリ確保が発生する
final titleStyle = TextStyle(fontSize: 16);
// 良い例:一度のコンパイルでメモリ上に固定配置される
const titleStyle = TextStyle(fontSize: 16);
`const`を使うことで、その値は実行時に何度もインスタンス化されることはない。メモリ上の特定のアドレスを再利用する(Canonicalization)。Flutterにおいて`const`ウィジェットを多用すべき理由は、単なる微々たる最適化ではなく、不要なObject Allocation(メモリ確保)とGarbage Collection(GC)の負荷を根本から断つためだ。
—
2. `final`:ランタイムの不変性(Immutability)
`final`は「代入は一度きり」を保証する。しかし、`const`とは決定的に異なる。`final`は実行時に値が決まる。
APIから取得したレスポンスを保持するオブジェクト、あるいはコンストラクタ引数で注入される依存関係などは、`const`にはなり得ない。ここで`final`を使うことは、状態の変化を許さないという「設計上の契約」を宣言することと同義だ。
実務での設計指針:データの「流動性」を分離する
非同期API連携を行う際の美しいパターンを紹介しよう。
class UserProfile {
// 実行時に決まる値はfinalにする
final String id;
final String name;
// コンストラクタをconstにすることで、このクラスのインスタンスを
// constとして扱える余地を残す(非常に重要)
const UserProfile({required this.id, required this.name});
}
// APIからの取得データはランタイムで決まるためfinal
final user = await fetchUser();
`final`で宣言された変数は、Dart VM上で最適化の対象となる。コンパイラは「この変数は後から変更されない」ことを知っているため、型推論やインライン展開において強固な根拠を得る。
—
3. `var`:型推論という名の「動的な柔軟性」
`var`は悪ではない。むしろ、スコープが限定された一時変数やループ変数において、過剰な型指定を避けることはコードの可読性を高める。
ただし、「再代入を前提とするか否か」という一点において、`var`を使うべきか、`final`にすべきかを厳格に線引きせよ。
現場のコードレビューで指摘すべきポイント
「なぜこの変数は`var`なのか?」という問いに対し、「後で変わるかもしれないから」という回答は、多くの場合「設計の甘さ」の現れだ。
// 良くない例:後で変わる可能性を放置している
var total = 0;
for (final item in items) {
total += item.price;
}
// 良い例:不変性を担保し、パイプラインで処理する
final total = items.fold(0, (sum, item) => sum + item.price);
`fold`や`map`を用いた関数型アプローチは、一時的な変数を排除し、コンパイラに対して「この変数は不変である」という情報をより強く与えることができる。
—
結論:Dartを使いこなすための「3つの規律」
私が現場でエンジニアに求めているのは、以下の3つのルールだ。
1. デフォルトは `const` とする:
リテラルやUIパーツはまず`const`を検討せよ。メモリを汚さないコードが最も速い。
2. 実行時に確定するなら `final` を徹底する:
状態の変更を許さないことは、バグを未然に防ぐ最高のアーキテクチャだ。
3. `var`は極めて限定的なスコープの一時変数にのみ許す:
`var`が見えたら「なぜ再代入が必要なのか?」を自問せよ。
Dartは現代的な言語の中でも、静的解析と動的実行のバランスを高度にチューニングできるツールだ。あなたが書く一行の変数が、VMのメモリ管理を最適化し、ユーザーの端末で最高のパフォーマンスを発揮する。
その自負を持って、コードを書いてほしい。Dartの深淵は、まだ入り口に立ったばかりだ。