0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Dart】クロージャの仕組みとメモリ保持

Last updated at Posted at 2025-10-21

はじめに

「関数が終わったのに、なぜ変数が消えないの?」
その謎を解く鍵が クロージャ(closure) にあります。


クロージャとは

クロージャ(closure) とは、
「関数」と「その関数が定義されたスコープの変数環境」をセットで保持する仕組みです。

つまり、「関数が外の変数を覚えている」状態。


簡単な例

Function makeCounter() {
  int count = 0; // 外側の変数

  return () {
    count++;
    print(count);
  };
}

void main() {
  var counter = makeCounter(); // makeCounter() はここで一度だけ呼ばれる
  counter(); // => 1
  counter(); // => 2
  counter(); // => 3
}

ここでのポイント

  • countmakeCounter() 内で定義されているローカル変数。
  • 通常なら makeCounter() が終わると count は破棄されるはず。
  • しかし counter() が呼ばれるたびに count は増えている。

関数終了後も変数が保持されている
これがクロージャの力です。


クロージャの仕組み(内部動作)

① 定義時にスコープを“閉じ込める”

関数が定義されるとき、
その関数は 自分がアクセスできる変数(環境) を一緒に「捕まえ」ます。

int x = 10;
Function makeAdder(int n) {
  return (int y) => x + n + y;
}

このとき、(int y) => x + n + y という関数は以下を記憶しています:

  • グローバル変数 x
  • 引数 n(makeAdderのローカル変数)

これを 環境(Environment) と呼びます。


② 実行後もメモリ上に残る

void main() {
  var add5 = makeAdder(5);
  print(add5(3)); // => 18
}

makeAdder(5) が終わっても、
n = 5 の情報は破棄されません。
なぜなら、返された関数がその変数を参照しているからです。

この仕組みのために、Dart のランタイムは
n の値をヒープ(heap)領域に退避して保持します。


メモリの仕組み:スタックからヒープへ

通常、関数のローカル変数はスタック(stack) に保存され、
関数が終了すると破棄されます。

しかし、クロージャにキャプチャされた変数は破棄できません。
なぜなら、その関数が外でも使われる可能性があるから。

このためDart(や他の言語)では:

状況 保存場所
通常のローカル変数 スタック(関数終了で破棄)
クロージャでキャプチャされた変数 ヒープ(関数終了後も保持)

イメージ図

makeCounter() が終わっても count無名関数 によって参照されているため解放されない。


メモリリークとガーベジコレクション(GC)

クロージャによる変数保持は非常に便利ですが、
参照が残り続けるとメモリリークの原因にもなります。

例えば:

List<Function> callbacks = [];

void registerCallback() {
  int counter = 0;
  callbacks.add(() {
    counter++;
    print(counter);
  });
}

void main() {
  for (int i = 0; i < 1000; i++) {
    registerCallback();
  }
  // callbacks が残り続ける限り、1000個の counter がヒープに保持される
}

対策:

  • 不要になったクロージャをリストから削除する
  • callbacks.clear() を呼び出してGCに開放させる

Dart のガーベジコレクタは、
参照が切れた時点でヒープ上の変数を自動的に解放します。


まとめ

項目 内容
定義 クロージャは「関数+スコープ環境」を保持するオブジェクト
特徴 外部関数が終了してもローカル変数を保持できる
メモリ動作 キャプチャされた変数はヒープ領域に退避
用途 カウンタ・状態保持・イベントハンドラなど
注意点 不要な参照を残すとメモリリークの原因になる

クロージャは、DartやJavaScriptにおける
「状態を持つ関数」を作るための最も重要な概念です。
レキシカルスコープに基づく環境キャプチャによって、
関数が実行を超えて変数を保持できるのです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?