【実務・中級編】Dartの定数式(Constant Expressions)で実行可能な演算の全貌 – Dart コア文法・オブジェクト指向・Null安全解析バイブル

こんにちは。テクニカルリードの私だ。
今日のコードレビューで、またこんな「動くけれどパフォーマンスをドブに捨ている」ような定数定義を見かけた。

// ❌ 開発現場でよく見かける「なんちゃってconst」
class UIConfig {
static const spacing = 16.0;
// おい、これは本当にコンパイル時定数か?
static final defaultPadding = EdgeInsets.all(spacing);
}

「動いてるんだからいいじゃないか」と思ったそこの君。致命的にDartのVMとAOTコンパイラの挙動を誤解している。
`final`は「代入が1回だけ可能」なランタイム変数だ。つまり、アプリが起動し、そのクラスがロードされるたびにメモリ上でオブジェクトのインスタンス化が走る。
一方、`const`はコンパイル時定数(Canonicalized Constant)だ。Dartのコンパイラがビルド時に値を完全に解決し、バイナリのデータセグメントに焼き込む。実行時のコストは「ゼロ」だ。FlutterのUIツリーにおいて、これがどれほどのメモリ効率とガベージコレクション(GC)の負荷軽減をもたらすか、想像に難くないはずだ。

今日は、Dartの `const` 式(Constant Expressions)で何が許され、コンパイル時にどう評価されるのか、その全貌を骨の髄まで叩き込む。

—

1. Dartのコンパイル時定数(const)を支える本質

Dartの `const` は、単なる「書き換え不可の変数」ではない。
コンパイル時に Constant Canonicalization(正準化) と呼ばれる最適化を受ける。同じ値を持つ `const` オブジェクトは、メモリ上の同一インスタンスを指すようにコンパイラによって強制される。

const a = Color(0xFF000000);
const b = Color(0xFF000000);
// trueになる。identical() すなわちメモリアドレスが完全に一致する。
print(identical(a, b));

この神業的な最適化を享受するためには、「定数式(Constant Expressions)」のルールを完全にハックしている必要がある。 評価のタイミングはすべて「コンパイル時」。つまり、実行時の関数呼び出しや、I/O、非同期処理などは一切介入できない。

—

2. constコンストラクタ内で許容される演算の全貌

では、`const` の世界では一体どこまでの計算が許されるのか?
結論から言えば、「純粋関数的であり、副作用がなく、入力がすべてコンパイル時定数である演算」はすべて許可される。

① プリミティブな算術・論理演算

四則演算(`+`, `-`, “, `/`, `~/`, `%`)、ビット演算(`&`, `|`, `^`, `<<`, `>>`)、比較演算子、論理演算(`&&`, `||`, `!`)はすべてコンパイル時にDart VMのホスト側(コンパイラ)で事前計算される。

const int bits = 1 << 3; // 8 がコンパイル時に計算される const bool isValid = (10 > 5) && !false; // true

② 三項演算子と条件分岐

コンパイル時定数式における三項演算子は完全にサポートされている。

const bool isProduction = bool.fromEnvironment(‘dart.vm.product’);
const int timeout = isProduction ? 30 : 5; // ビルド環境に応じて確定

③ コレクションリテラル(List, Set, Map)の構築

Dart 2.12以降、`const` コレクションは強力になった。ただし、要素もすべて `const` でなければならない。

const List endpoints = [
‘https://api.example.com/v1’,
‘https://auth.example.com’,
];

const Map httpCodes = {
‘OK’: 200,
‘BadRequest’: 400,
‘Unauthorized’: 401,
};

④ `const` コンストラクタによるオブジェクト生成と型キャスト

これが本丸だ。クラスに `const` コンストラクタを定義する場合、すべてのインスタンス変数が `final` でなければならない。 さらに、スーパークラスのコンストラクタも `const` である必要がある。

—

3. 【実践】プロダクションコードで使う堅牢な設計パターン

ここからが本番だ。Webフロントエンド開発やAPI連携を見据え、保守性が高く、一切無駄なオブジェクトを生成しない「堅牢なUIデザインコンフィグレーション」の実装例を提示する。

以下のコードは、そのままプロダクションコードとしてコピペして使える品質に仕上げてある。

import ‘package:flutter/material.dart’;

@immutable
class AppSpacing {
// プライベートコンストラクタでインスタンス化を隠蔽
const AppSpacing._();

static const double _base = 8.0;

// 算術演算はすべてコンパイル時に評価される
static const double xs = _base 0.5; // 4.0
static const double sm = _base; // 8.0
static const double md = _base 2.0; // 16.0
static const double lg = _base 3.0; // 24.0
static const double xl = _base 4.0; // 32.0
}

@immutable
class AppThemeConfig {
const AppThemeConfig._();

// Colorオブジェクトもコンパイル時定数として定義
static const Color primaryBlue = Color(0xFF1E88E5);
static const Color surfaceWhite = Color(0xFFFFFFFF);

// EdgeInsetsもconstコンストラクタで完全に定数化
static const EdgeInsets screenPadding = EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.lg,
);

// 【重要】カスタムconstクラスのネスト
// 画面ごとのレイアウト制約をコンパイル時に確定させる
static const Duration defaultAnimationDuration = Duration(milliseconds: 300);
}

/// 実際のコンポーネント設計での活用例
class RobustProductCard extends StatelessWidget {
const RobustProductCard({
super.key,
required this.title,
});

final String title;

@override
Widget build(BuildContext context) {
// ここで生成されるウィジェットツリーの大部分は、
// 親が再描画されてもconstのおかげで再評価(Rebuild)の対象外になる。
return Container(
padding: AppThemeConfig.screenPadding,
decoration: BoxDecoration(
color: AppThemeConfig.surfaceWhite,
borderRadius: BorderRadius.circular(AppSpacing.sm),
boxShadow: const [
BoxShadow(
color: Color(0x1A000000),
blurRadius: AppSpacing.sm,
offset: Offset(0, AppSpacing.xs),
),
],
),
child: Text(
title,
style: const TextStyle(
color: AppThemeConfig.primaryBlue,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
}
}

—

4. チーフアーキテクトからの警告:やってはいけない「アンチパターン」

定数式を使おうとして、よくやりがちなバグや非効率なコードを挙げておく。コードレビューでこれを見つけたら即座に差し戻すこと。

❌ アンチパターン 1: ランタイム関数やメソッドの呼び出し混入

// コンパイルエラー: DateTime.now() は実行時に関数が評価されるためconstにはできない
// static const now = DateTime.now();

// 【正しいアプローチ】
// 動的な値やランタイム依存の値は final を使う
static final DateTime launchTime = DateTime.now();

❌ アンチパターン 2: 非定数な変数をコレクションに含める

String dynamicName = “User”;

// コンパイルエラー: dynamicName は const ではない
// const List names = [dynamicName, “Admin”];

❌ アンチパターン 3: 不必要な `const` の乱用によるコンパイル肥大化

巨大なJSONパース結果や、動的に変化するデータ構造を無理やり `const` にしようとすること。`const` は不変で静的なデータ(デザインシステムの色、スペーシング、静的メッセージなど)に絞るべきだ。

—

5. まとめ

Dartの `const` 式は、単なる「お作法」ではない。
コンパイル時に計算を終わらせ、メモリの割り当てとGCの走査コストを極限まで削ぎ落とす、AOTコンパイラを味方につけた最強の最適化手法だ。

今日から君のコードベースにある `static final` のうち、値が静的に決まるものはすべて `static const` にリファクタリングしたまえ。
マシンにやらせるべき計算は、実行時ではなく、コンパイル時にすべて終わらせる。それがプロフェッショナルのコードだ。

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